try to index media immediately on import
Since indexing is supplemental, it can be made a background task that can occur at more or less any time. So we run it as soon as possible.
This commit is contained in:
@@ -14,13 +14,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(index)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/task.h
|
||||
task/task.cpp
|
||||
task/taskmanager.h
|
||||
task/taskmanager.cpp
|
||||
task/taskthread.h
|
||||
task/taskthread.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/index/index.h
|
||||
task/index/index.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "index.h"
|
||||
|
||||
#include "codec/decoder.h"
|
||||
|
||||
IndexTask::IndexTask(StreamPtr stream) :
|
||||
stream_(stream)
|
||||
{
|
||||
SetTitle(tr("Indexing %1:%2").arg(stream_->footage()->filename(), QString::number(stream_->index())));
|
||||
}
|
||||
|
||||
void IndexTask::Action()
|
||||
{
|
||||
if (stream_->footage()->decoder().isEmpty()) {
|
||||
emit Failed(QStringLiteral("Stream has no decoder"));
|
||||
} else {
|
||||
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
|
||||
|
||||
decoder->set_stream(stream_);
|
||||
|
||||
decoder->Open();
|
||||
decoder->Index();
|
||||
decoder->Close();
|
||||
|
||||
emit Succeeeded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef INDEXTASK_H
|
||||
#define INDEXTASK_H
|
||||
|
||||
#include "project/item/footage/footage.h"
|
||||
#include "task/task.h"
|
||||
|
||||
class IndexTask : public Task
|
||||
{
|
||||
public:
|
||||
IndexTask(StreamPtr stream);
|
||||
|
||||
protected:
|
||||
virtual void Action() override;
|
||||
|
||||
private:
|
||||
StreamPtr stream_;
|
||||
|
||||
};
|
||||
|
||||
#endif // INDEXTASK_H
|
||||
+10
-139
@@ -21,168 +21,39 @@
|
||||
#include "task.h"
|
||||
|
||||
Task::Task() :
|
||||
status_(kWaiting),
|
||||
thread_(this),
|
||||
text_(tr("Task")),
|
||||
title_(tr("Task")),
|
||||
cancelled_(false)
|
||||
{
|
||||
connect(&thread_, SIGNAL(finished()), this, SLOT(ThreadComplete()));
|
||||
}
|
||||
|
||||
bool Task::Start()
|
||||
void Task::Start()
|
||||
{
|
||||
// Tasks can only start if they're waiting
|
||||
if (status_ != kWaiting) {
|
||||
return false;
|
||||
}
|
||||
Action();
|
||||
|
||||
// Check if this task has any dependencies (tasks that should complete before this one starts)
|
||||
|
||||
for (int i=0;i<dependencies_.size();i++) {
|
||||
Task* dependency = dependencies_.at(i);
|
||||
|
||||
if (dependency->status() == kWaiting || dependency->status() == kWorking) {
|
||||
|
||||
// We need this task to finish before this task can start, so keep waiting
|
||||
return false;
|
||||
|
||||
} else if (dependency->status() == kError) {
|
||||
|
||||
// A dependency errored, so this task is likely invalid too
|
||||
set_error(tr("A dependency task failed"));
|
||||
set_status(kError);
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cancelled_ = false;
|
||||
|
||||
// Run Prologue() function
|
||||
if (!Prologue()) {
|
||||
set_status(kError);
|
||||
return false;
|
||||
}
|
||||
|
||||
set_status(kWorking);
|
||||
|
||||
thread_.start();
|
||||
|
||||
return true;
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
bool Task::Prologue()
|
||||
const QString &Task::GetTitle()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Task::Action()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Task::Epilogue()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const Task::Status &Task::status()
|
||||
{
|
||||
return status_;
|
||||
}
|
||||
|
||||
const QString &Task::text()
|
||||
{
|
||||
return text_;
|
||||
}
|
||||
|
||||
const QString &Task::error()
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
void Task::AddDependency(Task *dependency)
|
||||
{
|
||||
// Dependencies cannot be added if the Task is working or complete
|
||||
Q_ASSERT(status_ == kWaiting);
|
||||
|
||||
dependencies_.append(dependency);
|
||||
}
|
||||
|
||||
void Task::ResetState()
|
||||
{
|
||||
if (status_ == kWaiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status_ == kWorking) {
|
||||
Cancel();
|
||||
}
|
||||
|
||||
cancelled_ = false;
|
||||
|
||||
set_error(QString());
|
||||
|
||||
set_status(kWaiting);
|
||||
return title_;
|
||||
}
|
||||
|
||||
void Task::Cancel()
|
||||
{
|
||||
if (status_ == kWaiting) {
|
||||
set_status(kFinished);
|
||||
}
|
||||
|
||||
if (status_ != kWorking) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelled_ = true;
|
||||
|
||||
// FIXME: Should we limit the wait time?
|
||||
thread_.wait();
|
||||
}
|
||||
|
||||
void Task::set_error(const QString &s)
|
||||
void Task::SetErrorText(const QString &s)
|
||||
{
|
||||
error_ = s;
|
||||
}
|
||||
|
||||
void Task::set_text(const QString &s)
|
||||
void Task::SetTitle(const QString &s)
|
||||
{
|
||||
text_ = s;
|
||||
title_ = s;
|
||||
}
|
||||
|
||||
bool Task::cancelled()
|
||||
bool Task::IsCancelled()
|
||||
{
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
void Task::set_status(const Task::Status &status)
|
||||
{
|
||||
status_ = status;
|
||||
|
||||
if (status_ == kFinished || status_ == kError) {
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
emit StatusChanged(status_);
|
||||
}
|
||||
|
||||
void Task::ThreadComplete()
|
||||
{
|
||||
// thread_.result() will be set to the return value of Action()
|
||||
bool succeeded = thread_.result();
|
||||
|
||||
// Run the Prologue() function for any final tasks
|
||||
// User cancelling is not considered an error, so we need to check it too
|
||||
if (succeeded && !cancelled()) {
|
||||
succeeded = Epilogue();
|
||||
}
|
||||
|
||||
// If everything succeeded, we set the status accordingly
|
||||
if (succeeded) {
|
||||
set_status(kFinished);
|
||||
} else {
|
||||
set_status(kError);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-142
@@ -24,8 +24,6 @@
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
|
||||
#include "task/taskthread.h"
|
||||
|
||||
/**
|
||||
* @brief A base class for background tasks running in Olive.
|
||||
*
|
||||
@@ -47,31 +45,17 @@ class Task : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief The Status enum
|
||||
*
|
||||
* All states that a Task can be in. When subclassing, you don't need to set the Task's status as the base class
|
||||
* does that automatically.
|
||||
*/
|
||||
enum Status {
|
||||
/// This Task is yet to start
|
||||
kWaiting,
|
||||
|
||||
/// This Task is currently running (see Action())
|
||||
kWorking,
|
||||
|
||||
/// This Task has completed successfully
|
||||
kFinished,
|
||||
|
||||
/// This Task failed and could not complete
|
||||
kError
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Task Constructor
|
||||
*/
|
||||
Task();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the current title of this Task
|
||||
*/
|
||||
const QString& GetTitle();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Try to start this Task
|
||||
*
|
||||
@@ -81,29 +65,18 @@ public:
|
||||
* This function also checks its dependency Tasks and will only start if all of them are complete. If they are still
|
||||
* working, this function will return FALSE and the status will continue to be kWaiting. If any of them failed, this
|
||||
* Task will also fail - this function will return FALSE and the status will be set to kError.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if the Task started, FALSE if not.
|
||||
*/
|
||||
bool Start();
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* @brief Perform opening tasks before main Task thread begins
|
||||
* @brief Cancel the Task
|
||||
*
|
||||
* If a Task needs to perform any actions in the main thread before starting the Task's thread, (e.g. copying or
|
||||
* altering information) this function should be overridden and those actions should be performed here. It's
|
||||
* guaranteed that Prologue() will run in the main thread, and as such, functions here should remain as minimal as
|
||||
* possible as to not block the main thread for a noticeable amount of time. If your Task does not need any such
|
||||
* actions, you don't need to override this.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if the prologue was successful and we can start the Task now. If Prologue returns FALSE, the thread is never
|
||||
* created and Action()/Epilogue() are never run.
|
||||
* Sends a signal to the Task to stop as soon as possible. Always call this directly or connect with
|
||||
* Qt::DirectConnection, or else it'll be queued *after* the task has already finished.
|
||||
*/
|
||||
virtual bool Prologue();
|
||||
void Cancel();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The main Task function which is run in a separate thread
|
||||
*
|
||||
@@ -119,86 +92,15 @@ public:
|
||||
* could not finish, not if the Task found a negative result (see Task documentation for details). Before returning
|
||||
* FALSE, it's recommended to use set_error() to signal to the user what caused the failure.
|
||||
*/
|
||||
virtual bool Action();
|
||||
virtual void Action() = 0;
|
||||
|
||||
/**
|
||||
* @brief Perform any closing Tasks in the main thread after the Task thread finishes
|
||||
*
|
||||
* It's likely your Task modifies data used throughout the program in some way, and to prevent race conditions, it's
|
||||
* recommended to work with "copies" of that data in Action() (which is run in separate thread) and never
|
||||
* access/modify any data used in other threads. Then, after the Action() thread is complete, that data can be used to
|
||||
* "apply" that data in the main thread here.
|
||||
*
|
||||
* As this runs in the main thread, these functions shouldn't be kept fairly minimal to prevent blocking the main
|
||||
* thread.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if the Epilogue completed successfully. FALSE if not. A FALSE result here is considered a complete failure
|
||||
* of the Task, even though the bulk of the processing has been performed in Action().
|
||||
*/
|
||||
virtual bool Epilogue();
|
||||
|
||||
/**
|
||||
* @brief Current status of the Task
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A member of the Task::Status enum.
|
||||
*/
|
||||
const Status& status();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the current title of this Task
|
||||
*/
|
||||
const QString& text();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the current error message (empty if no error)
|
||||
*/
|
||||
const QString& error();
|
||||
|
||||
/**
|
||||
* @brief Add a dependency Task
|
||||
*
|
||||
* If another Task needs to complete before this one can begin, it can be added as a "dependency task". If a task
|
||||
* has dependencies, Start() will not start the task until the dependency tasks have all completed. If any of the
|
||||
* dependency tasks fail, this Task will also fail before starting.
|
||||
*
|
||||
* Dependencies can only be added if the Task is kWaiting.
|
||||
*
|
||||
* Naturally Tasks should never be dependent on each other. Circular dependencies will result in Tasks that never
|
||||
* begin.
|
||||
*
|
||||
* @param dependency
|
||||
*/
|
||||
void AddDependency(Task* dependency);
|
||||
|
||||
/**
|
||||
* @brief Reset this Task back to the waiting state
|
||||
*/
|
||||
void ResetState();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Cancel the Task
|
||||
*
|
||||
* Sends a signal to the Task to stop and waits for the Task to finish before returning. Tasks must be responsive to
|
||||
* cancelling so that the main thread doesn't halt for too long.
|
||||
*
|
||||
* Cancel()'s function is fairly simple, it sets cancelled_ to TRUE and waits for the thread to return. It's the
|
||||
* responsibility of the code in Action() to be able to respond quickly to cancelled_ changing.
|
||||
*/
|
||||
void Cancel();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Set the error message
|
||||
*
|
||||
* It is recommended to use this if your Action() function ever returns FALSE to tell the user why the failure
|
||||
* occurred.
|
||||
*/
|
||||
void set_error(const QString& s);
|
||||
void SetErrorText(const QString& s);
|
||||
|
||||
/**
|
||||
* @brief Set the Task title
|
||||
@@ -207,19 +109,14 @@ protected:
|
||||
* and shouldn't need to change during the life of the Task. To show an error message, it's recommended to use
|
||||
* set_error() instead.
|
||||
*/
|
||||
void set_text(const QString& s);
|
||||
void SetTitle(const QString& s);
|
||||
|
||||
/**
|
||||
* @brief Returns whether the thread has been explicitly cancelled or not
|
||||
*/
|
||||
bool cancelled();
|
||||
bool IsCancelled();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted whenever the Task status changes
|
||||
*/
|
||||
void StatusChanged(Task::Status s);
|
||||
|
||||
/**
|
||||
* @brief Signal emitted whenever progress is made
|
||||
*
|
||||
@@ -231,9 +128,10 @@ signals:
|
||||
*/
|
||||
void ProgressChanged(int p);
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when the Task finishes whether it succeeded or failed
|
||||
*/
|
||||
void Succeeeded();
|
||||
|
||||
void Failed(const QString& error);
|
||||
|
||||
void Finished();
|
||||
|
||||
/**
|
||||
@@ -242,30 +140,12 @@ signals:
|
||||
void Removed();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Set the status of this Task (also emits StatusChanged())
|
||||
*/
|
||||
void set_status(const Task::Status& status);
|
||||
|
||||
Status status_;
|
||||
|
||||
TaskThread thread_;
|
||||
|
||||
QString text_;
|
||||
QString title_;
|
||||
|
||||
QString error_;
|
||||
|
||||
QList<Task*> dependencies_;
|
||||
QAtomicInt cancelled_;
|
||||
|
||||
bool cancelled_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief A slot when the inner thread completes either successfully or unsuccessfully
|
||||
*/
|
||||
void ThreadComplete();
|
||||
};
|
||||
|
||||
using TaskPtr = std::shared_ptr<Task>;
|
||||
|
||||
#endif // TASK_H
|
||||
|
||||
+166
-66
@@ -23,119 +23,219 @@
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
TaskManager TaskManager::instance_;
|
||||
TaskManager* TaskManager::instance_ = nullptr;
|
||||
|
||||
TaskManager::TaskManager()
|
||||
TaskManager::TaskManager() :
|
||||
active_thread_count_(0)
|
||||
{
|
||||
maximum_task_count_ = QThread::idealThreadCount();
|
||||
// Initialize threads to run tasks on
|
||||
threads_.resize(QThread::idealThreadCount());
|
||||
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
QThread* t = new QThread(this);
|
||||
t->start(QThread::LowPriority);
|
||||
threads_.replace(i, {t, false});
|
||||
}
|
||||
}
|
||||
|
||||
TaskManager::~TaskManager()
|
||||
{
|
||||
Clear();
|
||||
// First send the signal to all tasks to start cancelling
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
if (task_info.status == kWorking) {
|
||||
task_info.task->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Next, signal each thread to quit as its next event in the queue
|
||||
foreach (const ThreadContainer& tc, threads_) {
|
||||
tc.thread->quit();
|
||||
}
|
||||
|
||||
// Wait for each thread's event queue to finish
|
||||
foreach (const ThreadContainer& tc, threads_) {
|
||||
tc.thread->wait();
|
||||
|
||||
// This is technically unnecessary since each QThread is a child of this object, but we may as well
|
||||
delete tc.thread;
|
||||
}
|
||||
|
||||
// Finally delete all task objects (they shouldn't have been deleted by TaskSucceeded() or TaskFailed() because our
|
||||
// event queue shouldn't be active by this point
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
delete task_info.task;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::CreateInstance()
|
||||
{
|
||||
instance_ = new TaskManager();
|
||||
}
|
||||
|
||||
void TaskManager::DestroyInstance()
|
||||
{
|
||||
delete instance_;
|
||||
}
|
||||
|
||||
TaskManager *TaskManager::instance()
|
||||
{
|
||||
return &instance_;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void TaskManager::AddTask(TaskPtr t)
|
||||
void TaskManager::AddTask(Task* t)
|
||||
{
|
||||
// Connect Task's status signal to the Callback
|
||||
connect(t.get(), SIGNAL(StatusChanged(Task::Status)), this, SLOT(TaskCallback(Task::Status)));
|
||||
connect(t, &Task::Succeeeded, this, &TaskManager::TaskSucceeded, Qt::QueuedConnection);
|
||||
connect(t, &Task::Failed, this, &TaskManager::TaskFailed, Qt::QueuedConnection);
|
||||
|
||||
// Add the Task to the queue
|
||||
tasks_.append(t);
|
||||
tasks_.append({t, kWaiting});
|
||||
|
||||
// Emit signal that a Task was added
|
||||
emit TaskAdded(t.get());
|
||||
emit TaskAdded(t);
|
||||
|
||||
// Scan through queue and start any Tasks that can (including this one)
|
||||
StartNextWaiting();
|
||||
}
|
||||
|
||||
void TaskManager::Clear()
|
||||
{
|
||||
// Delete Tasks from memory
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
tasks_.at(i)->Cancel();
|
||||
}
|
||||
tasks_.clear();
|
||||
}
|
||||
|
||||
void TaskManager::StartNextWaiting()
|
||||
{
|
||||
// Count the tasks that are currently active
|
||||
int working_count = 0;
|
||||
// If there are no tasks in the queue, there is nothing to be done
|
||||
if (tasks_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
TaskPtr t = tasks_.at(i);
|
||||
|
||||
if (t->status() == Task::kWorking) {
|
||||
|
||||
// Task is active, add it to the count
|
||||
working_count++;
|
||||
|
||||
} else if (t->status() == Task::kWaiting) {
|
||||
|
||||
// Task is waiting and we have available threads, try to start it
|
||||
if (t->Start()) {
|
||||
// If it started, add it to the working count
|
||||
working_count++;
|
||||
}
|
||||
// If all threads are occupied, nothing to be done
|
||||
if (active_thread_count_ == threads_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a list of tasks that are waiting
|
||||
QList<Task*> waiting_tasks;
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
if (task_info.status == kWaiting) {
|
||||
waiting_tasks.append(task_info.task);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the count exceeds our maximum threads, if so stop here
|
||||
if (working_count == maximum_task_count_) {
|
||||
break;
|
||||
// No tasks waiting to start
|
||||
if (waiting_tasks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For any inactive threads,
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
if (!threads_.at(i).active) {
|
||||
// This thread is inactive and needs a new Task
|
||||
Task* task = waiting_tasks.takeFirst();
|
||||
|
||||
task->moveToThread(threads_.at(i).thread);
|
||||
|
||||
threads_[i].active = true;
|
||||
active_thread_count_++;
|
||||
|
||||
SetTaskStatus(task, kWorking);
|
||||
|
||||
QMetaObject::invokeMethod(task,
|
||||
"Start",
|
||||
Qt::QueuedConnection);
|
||||
|
||||
if (active_thread_count_ == threads_.size() || waiting_tasks.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::DeleteTask(Task *t)
|
||||
{
|
||||
// Cancel the task
|
||||
t->Cancel();
|
||||
if (GetTaskStatus(t) == kWorking) {
|
||||
// Send a signal to the task to cancel, it will likely continue to cancel in the background after it's removed
|
||||
t->Cancel();
|
||||
}
|
||||
|
||||
// Remove instances of Task from queue
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
if (tasks_.at(i).get() == t) {
|
||||
emit t->Removed();
|
||||
if (tasks_.at(i).task == t) {
|
||||
tasks_.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit t->Removed();
|
||||
|
||||
if (GetTaskStatus(t) != kWorking) {
|
||||
// If the task isn't doing anything, we can simply delete it
|
||||
delete t;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::TaskFinished(Task* task)
|
||||
{
|
||||
// Set this thread's active value to false
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
if (threads_.at(i).thread == task->thread()) {
|
||||
threads_[i].active = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement the active thread count
|
||||
active_thread_count_--;
|
||||
}
|
||||
|
||||
TaskManager::TaskStatus TaskManager::GetTaskStatus(Task *t)
|
||||
{
|
||||
foreach (const TaskContainer& container, tasks_) {
|
||||
if (container.task == t) {
|
||||
return container.status;
|
||||
}
|
||||
}
|
||||
|
||||
return kError;
|
||||
}
|
||||
|
||||
void TaskManager::SetTaskStatus(Task *t, TaskStatus status)
|
||||
{
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
TaskContainer& cont = tasks_[i];
|
||||
|
||||
if (cont.task == t) {
|
||||
cont.status = status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::TaskCallback(Task::Status status)
|
||||
void TaskManager::TaskSucceeded()
|
||||
{
|
||||
if (status == Task::kFinished || status == Task::kError) {
|
||||
// The Task has finished, we can start a new one
|
||||
StartNextWaiting();
|
||||
Task* task_sender = static_cast<Task*>(sender());
|
||||
|
||||
if (status == Task::kFinished) {
|
||||
// The Task was successful, remove this Task from the queue
|
||||
DeleteTask(static_cast<Task*>(sender()));
|
||||
SetTaskStatus(task_sender, kFinished);
|
||||
|
||||
TaskFinished(task_sender);
|
||||
|
||||
// Delete this task
|
||||
DeleteTask(task_sender);
|
||||
}
|
||||
|
||||
void TaskManager::TaskFailed()
|
||||
{
|
||||
Task* task_sender = static_cast<Task*>(sender());
|
||||
|
||||
SetTaskStatus(task_sender, kError);
|
||||
|
||||
TaskFinished(task_sender);
|
||||
|
||||
// If this task has already been deleted, we'll free its memory now
|
||||
bool was_deleted = true;
|
||||
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
if (tasks_.at(i).task == task_sender) {
|
||||
was_deleted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TaskManager::AddTaskCommand::AddTaskCommand(TaskPtr t, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
task_(t)
|
||||
{
|
||||
}
|
||||
|
||||
void TaskManager::AddTaskCommand::redo()
|
||||
{
|
||||
TaskManager::instance()->AddTask(task_);
|
||||
}
|
||||
|
||||
void TaskManager::AddTaskCommand::undo()
|
||||
{
|
||||
TaskManager::instance()->DeleteTask(task_.get());
|
||||
|
||||
task_->ResetState();
|
||||
if (was_deleted) {
|
||||
delete task_sender;
|
||||
}
|
||||
}
|
||||
|
||||
+53
-66
@@ -24,6 +24,7 @@
|
||||
#include <QVector>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "common/constructors.h"
|
||||
#include "task/task.h"
|
||||
|
||||
/**
|
||||
@@ -50,25 +51,11 @@ public:
|
||||
*/
|
||||
virtual ~TaskManager();
|
||||
|
||||
/**
|
||||
* @brief Deleted copy constructor
|
||||
*/
|
||||
TaskManager(const TaskManager& other) = delete;
|
||||
DISABLE_COPY_MOVE(TaskManager)
|
||||
|
||||
/**
|
||||
* @brief Deleted move constructor
|
||||
*/
|
||||
TaskManager(TaskManager&& other) = delete;
|
||||
static void CreateInstance();
|
||||
|
||||
/**
|
||||
* @brief Deleted copy assignment
|
||||
*/
|
||||
TaskManager& operator=(const TaskManager& other) = delete;
|
||||
|
||||
/**
|
||||
* @brief Deleted move assignment
|
||||
*/
|
||||
TaskManager& operator=(TaskManager&& other) = delete;
|
||||
static void DestroyInstance();
|
||||
|
||||
static TaskManager* instance();
|
||||
|
||||
@@ -87,38 +74,7 @@ public:
|
||||
*
|
||||
* The task to add and run. TaskManager takes ownership of this Task and will be responsible for freeing it.
|
||||
*/
|
||||
void AddTask(TaskPtr t);
|
||||
|
||||
/**
|
||||
* @brief Forcibly cancel all commands and clear them
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Undoable command for adding a Task to the TaskManager
|
||||
*/
|
||||
class AddTaskCommand : public QUndoCommand {
|
||||
public:
|
||||
AddTaskCommand(TaskPtr t, QUndoCommand* parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Adds the Task to the TaskManager
|
||||
*
|
||||
* If there are available threads, TaskManager will start running it.
|
||||
*/
|
||||
virtual void redo() override;
|
||||
|
||||
/**
|
||||
* @brief Undoes adding the Task
|
||||
*
|
||||
* If the Task is running, it is cancelled. Then the Task is removed from the TaskManager and the Task's state is
|
||||
* reset.
|
||||
*/
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TaskPtr task_;
|
||||
};
|
||||
void AddTask(Task *t);
|
||||
|
||||
signals:
|
||||
/**
|
||||
@@ -131,6 +87,36 @@ signals:
|
||||
void TaskAdded(Task* t);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The Status enum
|
||||
*
|
||||
* All states that a Task can be in. When subclassing, you don't need to set the Task's status as the base class
|
||||
* does that automatically.
|
||||
*/
|
||||
enum TaskStatus {
|
||||
/// This Task is yet to start
|
||||
kWaiting,
|
||||
|
||||
/// This Task is currently running (see Action())
|
||||
kWorking,
|
||||
|
||||
/// This Task has completed successfully
|
||||
kFinished,
|
||||
|
||||
/// This Task failed and could not complete
|
||||
kError
|
||||
};
|
||||
|
||||
struct TaskContainer {
|
||||
Task* task;
|
||||
TaskStatus status;
|
||||
};
|
||||
|
||||
struct ThreadContainer {
|
||||
QThread* thread;
|
||||
bool active;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Scan through the task queue and start any Tasks that are able to start
|
||||
*
|
||||
@@ -155,35 +141,36 @@ private:
|
||||
*/
|
||||
void DeleteTask(Task* t);
|
||||
|
||||
void TaskFinished(Task *task);
|
||||
|
||||
TaskStatus GetTaskStatus(Task* t);
|
||||
|
||||
void SetTaskStatus(Task* t, TaskStatus status);
|
||||
|
||||
/**
|
||||
* @brief Internal task array
|
||||
*/
|
||||
QVector<TaskPtr> tasks_;
|
||||
QVector<TaskContainer> tasks_;
|
||||
|
||||
/**
|
||||
* @brief Constant set at run-time of how many Tasks can run concurrently
|
||||
*
|
||||
* Currently set in the Constructor to QThread::idealThreadCount()
|
||||
* @brief Background threads to run tasks on
|
||||
*/
|
||||
int maximum_task_count_;
|
||||
QVector<ThreadContainer> threads_;
|
||||
|
||||
/**
|
||||
* @brief Value for how many threads are currently active
|
||||
*/
|
||||
int active_thread_count_;
|
||||
|
||||
/**
|
||||
* @brief TaskManager singleton instance
|
||||
*/
|
||||
static TaskManager instance_;
|
||||
static TaskManager* instance_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Callback when a Task's status changes
|
||||
*
|
||||
* When a Task is added, it's connected to this so TaskManager is alerted whenever a Task's status changes. It can
|
||||
* therefore keep track of Tasks starting, completing, or failing.
|
||||
*
|
||||
* @param status
|
||||
*
|
||||
* The new status of the Task
|
||||
*/
|
||||
void TaskCallback(Task::Status status);
|
||||
void TaskSucceeded();
|
||||
|
||||
void TaskFailed();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
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 "taskthread.h"
|
||||
|
||||
#include "task/task.h"
|
||||
|
||||
TaskThread::TaskThread(Task *parent) :
|
||||
parent_(parent),
|
||||
result_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void TaskThread::run()
|
||||
{
|
||||
result_ = parent_->Action();
|
||||
}
|
||||
|
||||
bool TaskThread::result()
|
||||
{
|
||||
return result_;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
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 TASKTHREAD_H
|
||||
#define TASKTHREAD_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class Task;
|
||||
|
||||
/**
|
||||
* @brief An internal class only used by Task.
|
||||
*
|
||||
* TaskThread is a simple QThread subclass designed to create a thread and run the
|
||||
* Task's Action() function. It also stores the result of Action() which can be read using result() when the thread
|
||||
* signals that it has finished().
|
||||
*/
|
||||
class TaskThread : public QThread
|
||||
{
|
||||
public:
|
||||
TaskThread(Task* parent);
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
bool result();
|
||||
|
||||
private:
|
||||
Task* parent_;
|
||||
|
||||
bool result_;
|
||||
};
|
||||
|
||||
#endif // TASKTHREAD_H
|
||||
Reference in New Issue
Block a user