Commit d7e914ba authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Start proper replacement for JobManager

parent 3c919550
......@@ -4055,7 +4055,7 @@ void Bin::reloadAllProducers(bool reloadThumbs)
clip->discardAudioThumb();
// We need to set a temporary id before all outdated producers are replaced;
//int jobId = pCore->jobManager()->startJob<LoadJob>({clip->clipId()}, -1, QString(), xml);
ClipLoadTask::start(clip->clipId(), xml, false, this);
ClipLoadTask::start({ObjectType::BinClip,clip->clipId().toInt()}, xml, false, this);
if (reloadThumbs) {
ThumbnailCache::get()->invalidateThumbsForClip(clip->clipId());
}
......@@ -4073,7 +4073,7 @@ void Bin::checkAudioThumbs()
for (const auto &clip : qAsConst(clipList)) {
ClipType::ProducerType type = clip->clipType();
if (type == ClipType::AV || type == ClipType::Audio || type == ClipType::Playlist || type == ClipType::Unknown) {
AudioLevelsTask::start(clip->clipId(), this, false);
AudioLevelsTask::start({ObjectType::BinClip, clip->clipId().toInt()}, this, false);
}
}
}
......
......@@ -120,8 +120,10 @@ ProjectClip::ProjectClip(const QString &id, const QIcon &thumb, const std::share
AbstractProjectItem::setRating(uint(getProducerIntProperty(QStringLiteral("kdenlive:rating"))));
connectEffectStack();
if (m_clipStatus == FileStatus::StatusProxy || m_clipStatus == FileStatus::StatusReady || m_clipStatus == FileStatus::StatusProxyOnly) {
ClipLoadTask::start(m_binId, QDomElement(), true, this);
AudioLevelsTask::start(m_binId, this, false);
// Generate clip thumbnail
ClipLoadTask::start({ObjectType::BinClip,m_binId.toInt()}, QDomElement(), true, this);
// Generate audio levels
AudioLevelsTask::start({ObjectType::BinClip, m_binId.toInt()}, this, false);
}
}
......@@ -372,7 +374,6 @@ size_t ProjectClip::frameDuration() const
void ProjectClip::reloadProducer(bool refreshOnly, bool isProxy, bool forceAudioReload)
{
// we find if there are some loading job on that clip
int loadjobId = -1;
//pCore->jobManager()->hasPendingJob(clipId(), AbstractClipJob::LOADJOB, &loadjobId);
QMutexLocker lock(&m_thumbMutex);
if (refreshOnly) {
......@@ -381,14 +382,13 @@ void ProjectClip::reloadProducer(bool refreshOnly, bool isProxy, bool forceAudio
// Clear cache first
ThumbnailCache::get()->invalidateThumbsForClip(clipId());
//pCore->jobManager()->discardJobs(clipId(), AbstractClipJob::THUMBJOB);
ClipLoadTask::cancel(m_binId);
pCore->taskManager.discardJobs({ObjectType::BinClip, m_binId.toInt()}, AbstractTask::LOADJOB);
m_thumbsProducer.reset();
ClipLoadTask::start(m_binId, QDomElement(), true, this);
ClipLoadTask::start({ObjectType::BinClip,m_binId.toInt()}, QDomElement(), true, this);
//emit pCore->jobManager()->startJob<ThumbJob>({clipId()}, loadjobId, QString(), -1, true, true);
} else {
// If another load job is running?
ClipLoadTask::cancel(m_binId);
AudioLevelsTask::cancel(m_binId);
pCore->taskManager.discardJobs({ObjectType::BinClip, m_binId.toInt()});
if (QFile::exists(m_path) && !hasProxy()) {
clearBackupProperties();
}
......@@ -413,7 +413,7 @@ void ProjectClip::reloadProducer(bool refreshOnly, bool isProxy, bool forceAudio
}
ThumbnailCache::get()->invalidateThumbsForClip(clipId());
ClipLoadTask::start(m_binId, xml, false, this);
ClipLoadTask::start({ObjectType::BinClip,m_binId.toInt()}, xml, false, this);
//int loadJob = pCore->jobManager()->startJob<LoadJob>({clipId()}, loadjobId, QString(), xml);
//emit pCore->jobManager()->startJob<ThumbJob>({clipId()}, loadJob, QString(), -1, true, true);
if (forceAudioReload || (!isProxy && hashChanged)) {
......@@ -532,8 +532,8 @@ bool ProjectClip::setProducer(std::shared_ptr<Mlt::Producer> producer, bool repl
getFileHash();
// set parent again (some info need to be stored in producer)
updateParent(parentItem().lock());
ClipLoadTask::start(m_binId, QDomElement(), true, this);
AudioLevelsTask::start(m_binId, this, false);
ClipLoadTask::start({ObjectType::BinClip,m_binId.toInt()}, QDomElement(), true, this);
AudioLevelsTask::start({ObjectType::BinClip, m_binId.toInt()}, this, false);
if (pCore->currentDoc()->getDocumentProperty(QStringLiteral("enableproxy")).toInt() == 1) {
QList<std::shared_ptr<ProjectClip>> clipList;
......@@ -1483,7 +1483,7 @@ void ProjectClip::discardAudioThumb()
if (!m_audioInfo) {
return;
}
AudioLevelsTask::cancel(m_binId);
pCore->taskManager.discardJobs({ObjectType::BinClip, m_binId.toInt()});
QString audioThumbPath;
QList <int> streams = m_audioInfo->streams().keys();
// Delete audio thumbnail data
......
......@@ -605,7 +605,7 @@ bool ProjectItemModel::requestBinClipDeletion(const std::shared_ptr<AbstractProj
}
bool isSubClip = clip->itemType() == AbstractProjectItem::SubClipItem;
if (!isSubClip) {
AudioLevelsTask::cancel(clip->clipId());
pCore->taskManager.discardJobs({ObjectType::BinClip, clip->clipId().toInt()});
}
clip->selfSoftDelete(undo, redo);
int id = clip->getId();
......@@ -726,7 +726,7 @@ bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &descrip
ProjectClip::construct(id, description, m_blankThumb, std::static_pointer_cast<ProjectItemModel>(shared_from_this()));
bool res = addItem(new_clip, parentId, undo, redo);
if (res) {
ClipLoadTask::start(id, description, false, this);
ClipLoadTask::start({ObjectType::BinClip,id.toInt()}, description, false, this);
//int loadJob = emit pCore->jobManager()->startJob<LoadJob>({id}, -1, QString(), description, std::bind(readyCallBack, id));
int loadJob = -1;
//emit pCore->jobManager()->startJob<ThumbJob>({id}, loadJob, QString(), 0, true);
......
......@@ -46,10 +46,10 @@ the Free Software Foundation, either version 3 of the License, or
std::unique_ptr<Core> Core::m_self;
Core::Core()
: audioThumbCache(QStringLiteral("audioCache"), 2000000)
, taskManager(this)
, m_thumbProfile(nullptr)
, m_capture(new MediaCapture(this))
{
clipJobPool.setMaxThreadCount(qMin(4, QThread::idealThreadCount() - 1));
}
void Core::prepareShutdown()
......@@ -221,7 +221,6 @@ void Core::initGUI(bool isAppImage, const QString &MltPath, const QUrl &Url, con
}
QMetaObject::invokeMethod(pCore->projectManager(), "slotLoadOnOpen", Qt::QueuedConnection);
m_mainWindow->show();
QThreadPool::globalInstance()->setMaxThreadCount(qMin(4, QThreadPool::globalInstance()->maxThreadCount()));
// Release startup crash lock file
QFile lockFile(QDir::temp().absoluteFilePath(QStringLiteral("kdenlivelock")));
......
......@@ -12,6 +12,7 @@ the Free Software Foundation, either version 3 of the License, or
#define CORE_H
#include "definitions.h"
#include "jobs/taskmanager.h"
#include "kdenlivecore_export.h"
#include "undohelper.hpp"
#include <QMutex>
......@@ -72,9 +73,6 @@ public:
Core &operator=(Core &&) = delete;
~Core() override;
/* @brief The thread job pool for clip jobs, allowing to set a max number of concurrent jobs */
QThreadPool clipJobPool;
/**
* @brief Setup the basics of the application, in particular the connection
......@@ -251,6 +249,8 @@ public:
/** @brief Display key binding info in statusbar. */
void setWidgetKeyBinding(const QString &mess = QString());
KSharedDataCache audioThumbCache;
/* @brief The thread job pool for clip jobs, allowing to set a max number of concurrent jobs */
TaskManager taskManager;
/** @brief The number of clip load jobs changed */
void loadingClips(int);
......@@ -271,6 +271,7 @@ private:
SubtitleEdit *m_subtitleWidget{nullptr};
TextBasedEdit *m_textEditWidget{nullptr};
MixerManager *m_mixerWidget{nullptr};
/** @brief Current project's profile path */
QString m_currentProfile;
......
......@@ -2,6 +2,8 @@ set(kdenlive_SRCS
${kdenlive_SRCS}
jobs/abstractclipjob.cpp
jobs/audiothumbjob.cpp
jobs/abstracttask.cpp
jobs/taskmanager.cpp
jobs/audiolevelstask.cpp
jobs/cliploadtask.cpp
jobs/jobmanager.cpp
......
/*
* Copyright (c) 2013-2021 Meltytech, LLC
* Copyright (c) 2021 Jean-Baptiste Mardelle <jb@kdenlive.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "abstracttask.h"
#include "core.h"
#include "bin/projectitemmodel.h"
#include "bin/projectclip.h"
#include "audio/audioStreamInfo.h"
#include <QString>
#include <QVariantList>
#include <QImage>
#include <QList>
#include <QRgb>
#include <QThreadPool>
#include <QMutex>
#include <QTime>
#include <QFile>
#include <QElapsedTimer>
AbstractTask::AbstractTask(const ObjectId &owner, JOBTYPE type, QObject* object)
: QRunnable()
, m_owner(owner)
, m_object(object)
, m_progress(0)
, m_isCanceled(false)
, m_isForce(false)
, m_type(type)
{
setAutoDelete(true);
switch (type) {
case AbstractTask::LOADJOB:
m_priority = 10;
break;
case AbstractTask::TRANSCODEJOB:
case AbstractTask::PROXYJOB:
m_priority = 8;
break;
case AbstractTask::FILTERCLIPJOB:
case AbstractTask::STABILIZEJOB:
case AbstractTask::ANALYSECLIPJOB:
case AbstractTask::SPEEDJOB:
m_priority = 5;
break;
default:
m_priority = 5;
break;
}
}
const ObjectId AbstractTask::ownerId() const
{
return m_owner;
}
AbstractTask::~AbstractTask()
{
}
bool AbstractTask::operator==(AbstractTask &b)
{
return m_owner == b.ownerId();
}
void AbstractTask::run()
{
// 2 channels interleaved of uchar values
QMutexLocker lk(&m_runMutex);
}
/*
* Copyright (c) 2021 Jean-Baptiste Mardelle <jb@kdenlive.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ABSTRACTTASK_H
#define ABSTRACTTASK_H
#include "definitions.h"
#include <QRunnable>
#include <QMutex>
#include <QObject>
class AbstractTask : public QRunnable
{
friend class TaskManager;
public:
enum JOBTYPE {
NOJOBTYPE = 0,
PROXYJOB = 1,
CUTJOB = 2,
STABILIZEJOB = 3,
TRANSCODEJOB = 4,
FILTERCLIPJOB = 5,
THUMBJOB = 6,
ANALYSECLIPJOB = 7,
LOADJOB = 8,
AUDIOTHUMBJOB = 9,
SPEEDJOB = 10,
CACHEJOB = 11
};
AbstractTask(const ObjectId &owner, JOBTYPE type, QObject* object);
virtual ~AbstractTask();
static void closeAll();
const ObjectId ownerId() const;
bool operator==(AbstractTask& b);
protected:
ObjectId m_owner;
QObject* m_object;
int m_progress;
bool m_successful;
bool m_isCanceled;
bool m_isForce;
void run() override;
void cleanup();
private:
//QString cacheKey();
JOBTYPE m_type;
int m_priority;
QMutex m_runMutex;
};
#endif // ABSTRACTTASK_H
......@@ -41,105 +41,46 @@ static void deleteQVariantList(QVector <uint8_t>* list)
delete list;
}
AudioLevelsTask::AudioLevelsTask(const QString &clipId, QObject* object)
: QRunnable()
, m_cid(clipId)
, m_object(object)
, m_isCanceled(false)
, m_isForce(false)
AudioLevelsTask::AudioLevelsTask(const ObjectId &owner, QObject* object)
: AbstractTask(owner, AbstractTask::AUDIOTHUMBJOB, object)
{
setAutoDelete(true);
}
const QString AudioLevelsTask::clipId() const
{
return m_cid;
}
AudioLevelsTask::~AudioLevelsTask()
{
}
void AudioLevelsTask::cancel(const QString cid)
void AudioLevelsTask::start(const ObjectId &owner, QObject* object, bool force)
{
QMutexLocker lk(&tasksListMutex);
AudioLevelsTask* task = new AudioLevelsTask(owner, object);
// See if there is already a task for this MLT service and resource.
foreach (AudioLevelsTask* t, tasksList) {
if (t->clipId() == cid) {
// If so, then just add ourselves to be notified upon completion.
t->m_isCanceled = true;
break;
}
}
}
void AudioLevelsTask::start(const QString cid, QObject* object, bool force)
{
AudioLevelsTask* task = new AudioLevelsTask(cid, object);
tasksListMutex.lock();
// See if there is already a task for this MLT service and resource.
foreach (AudioLevelsTask* t, tasksList) {
if (*t == *task) {
// If so, then just add ourselves to be notified upon completion.
delete task;
task = 0;
break;
}
if (pCore->taskManager.hasPendingJob(owner, AbstractTask::AUDIOTHUMBJOB)) {
delete task;
task = 0;
}
if (task) {
// Otherwise, start a new audio levels generation thread.
task->m_isForce = force;
tasksList << task;
pCore->clipJobPool.start(task, 1);
}
tasksListMutex.unlock();
}
void AudioLevelsTask::closeAll()
{
// Tell all of the audio levels tasks to stop.
tasksListMutex.lock();
while (!tasksList.isEmpty()) {
AudioLevelsTask* task = tasksList.first();
task->m_isCanceled = true;
tasksList.removeFirst();
}
tasksListMutex.unlock();
}
bool AudioLevelsTask::operator==(AudioLevelsTask &b)
{
return m_cid == b.clipId();
}
void AudioLevelsTask::cleanup()
{
tasksListMutex.lock();
for (int i = 0; i < tasksList.size(); ++i) {
if (*tasksList[i] == *this) {
tasksList.removeAt(i);
break;
}
pCore->taskManager.startTask(owner.second, task);
}
tasksListMutex.unlock();
}
void AudioLevelsTask::run()
{
// 2 channels interleaved of uchar values
if (m_isCanceled) {
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
return;
}
auto binClip = pCore->projectItemModel()->getClipByBinID(m_cid);
auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.second));
if (binClip == nullptr) {
// Clip was deleted
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
return;
}
if (binClip->audioChannels() == 0 || binClip->audioThumbCreated()) {
// nothing to do
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
return;
}
std::shared_ptr<Mlt::Producer> producer = binClip->originalProducer();
......@@ -147,13 +88,13 @@ void AudioLevelsTask::run()
/*m_errorMessage.append(i18n("Audio thumbs: cannot open project file %1", m_binClip->url()));
m_done = true;
m_successful = false;*/
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
return;
}
int lengthInFrames = producer->get_length(); // Multiply this if we want more than 1 sample per frame
if (lengthInFrames == INT_MAX) {
// This is a broken file or live feed, don't attempt to generate audio thumbnails
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
return;
}
int frequency = binClip->audioInfo()->samplingRate();
......@@ -197,7 +138,7 @@ void AudioLevelsTask::run()
producer->unlock();
qDebug()<<"=== FINISHED PRODUCING AUDIO FOR: "<<key<<", SIZE: "<<levelsCopy->size();
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail");
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
return;
}
}
......@@ -211,7 +152,7 @@ void AudioLevelsTask::run()
QScopedPointer<Mlt::Producer> audioProducer(new Mlt::Producer(*producer->profile(), service.toUtf8().constData(), producer->get("resource")));
if (!audioProducer->is_valid()) {
//m_errorMessage.append(i18n("Audio thumbs: cannot open file %1", producer->get("resource")));
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
return;
}
audioProducer->set("video_index", "-1");
......@@ -230,14 +171,13 @@ void AudioLevelsTask::run()
keys << "meta.media.audio_level." + QString::number(i);
}
uint maxLevel = 1;
int last_val = 0;
QElapsedTimer updateTime;
updateTime.start();
for (int z = 0; z < lengthInFrames && !m_isCanceled; ++z) {
int val = int(100.0 * z / lengthInFrames);
if (last_val != val) {
if (m_progress != val) {
m_progress = val;
QMetaObject::invokeMethod(m_object, "audioJobProgress", Q_ARG(const int, val));
last_val = val;
}
QScopedPointer<Mlt::Frame> mltFrame(audioProducer->get_frame());
if ((mltFrame != nullptr) && mltFrame->is_valid() && (mltFrame->get_int("test_audio") == 0)) {
......@@ -274,6 +214,7 @@ void AudioLevelsTask::run()
}*/
if (!m_isCanceled) {
m_progress = 100;
QMetaObject::invokeMethod(m_object, "audioJobProgress", Q_ARG(const int, 100));
if (mltLevels.size() > 0 && !m_isCanceled) {
QVector <uint8_t>* levelsCopy = new QVector <uint8_t>(mltLevels);
......@@ -305,5 +246,5 @@ void AudioLevelsTask::run()
}
}
}
cleanup();
pCore->taskManager.taskDone(m_owner.second, this);
}
......@@ -20,31 +20,21 @@
#ifndef AUDIOLEVELSTASK_H
#define AUDIOLEVELSTASK_H
#include "abstracttask.h"
#include <QRunnable>
#include <QObject>
class AudioLevelsTask : public QRunnable
class AudioLevelsTask : public AbstractTask
{
public:
AudioLevelsTask(const QString &clipId, QObject* object);
AudioLevelsTask(const ObjectId &owner, QObject* object);
virtual ~AudioLevelsTask();
static void start(const QString cid, QObject* object, bool force = false);
static void cancel(const QString cid);
static void closeAll();
const QString clipId() const;
bool operator==(AudioLevelsTask& b);
static void start(const ObjectId &owner, QObject* object, bool force = false);
protected:
void run() override;
private:
//QString cacheKey();
QString m_cid;
QObject* m_object;
bool m_successful;
bool m_isCanceled;
bool m_isForce;
void cleanup();
};
#endif // AUDIOLEVELSTASK_H
......@@ -28,14 +28,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "doc/kthumb.h"
#include "doc/kdenlivedoc.h"
#include "utils/thumbnailcache.hpp"
#include "project/dialogs/slideshowclip.h"
#include "xml/xml.hpp"
#include <QString>
#include <QVariantList>
#include <QImage>
#include <QList>
#include <QThreadPool>
#include <QMutex>
#include <QTime>
#include <QFile>
#include <QAction>
......@@ -46,105 +45,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <profiles/profilemodel.hpp>
#include <klocalizedstring.h>
#include <KMessageWidget>
#include <project/dialogs/slideshowclip.h>
#include <project/dialogs/slideshowclip.h>
static QList<ClipLoadTask*> tasksList;
static QMutex tasksListMutex;
static int maxJobs = 0;
ClipLoadTask::ClipLoadTask(const QString &clipId, const QDomElement &xml, bool thumbOnly, QObject* object, std::function<void()> readyCallBack)
: QRunnable()
, m_cid(clipId)
, m_object(object)
ClipLoadTask::ClipLoadTask(const ObjectId &owner, const QDomElement &xml, bool thumbOnly, QObject* object, std::function<void()> readyCallBack)
: AbstractTask(owner, AbstractTask::LOADJOB, object)
, m_xml(xml)
, m_thumbOnly(thumbOnly)
, m_readyCallBack(std::move(readyCallBack))
, m_isCanceled(false)
, m_isForce(false)
{
setAutoDelete(true);
}
const QString ClipLoadTask::clipId() const
{
return m_cid;
}
ClipLoadTask::~ClipLoadTask()
{
}
void ClipLoadTask::cancel(const QString cid)
{
QMutexLocker lk(&tasksListMutex);
// See if there is already a task for this MLT service and resource.
foreach (ClipLoadTask* t, tasksList) {
if (t->clipId() == cid) {
// If so, then just add ourselves to be notified upon completion.
t->m_isCanceled = true;
break;
}
}
pCore->loadingClips(100 * (maxJobs - tasksList.size()) / maxJobs);
}
void ClipLoadTask::start(const QString cid, const QDomElement &xml, bool thumbOnly, QObject* object, bool force, std::function<void()> readyCallBack)
void<