71 lines
3.6 KiB
C++
71 lines
3.6 KiB
C++
#pragma once
|
|
|
|
#include <condition_variable>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <atomic>
|
|
#include <utility>
|
|
|
|
namespace MultiThreading {
|
|
class TaskBase;
|
|
|
|
template <typename T>
|
|
class Task;
|
|
}
|
|
|
|
class MultiThreading::TaskBase {
|
|
protected:
|
|
std::function<void()> complete_callback = nullptr;
|
|
std::condition_variable completed_condition;
|
|
std::atomic<bool> complete = false;
|
|
std::mutex mtx;
|
|
void MarkTaskComplete() { std::lock_guard<std::mutex> lock(mtx); complete = true; completed_condition.notify_all(); }
|
|
protected:
|
|
explicit TaskBase(std::function<void()> task_complete_callback = nullptr) : complete_callback(std::move(task_complete_callback)) {};
|
|
public:
|
|
/// @returns whether the task is finished.
|
|
[[nodiscard]] bool Complete() const { return complete; }
|
|
|
|
/// Condition variable waits for the task to be complete
|
|
/// @note There is no way to know if the task has been assigned, If you do this and that never happens the calling thread will wait forever.
|
|
void WaitComplete() { std::unique_lock<std::mutex> lock(mtx); completed_condition.wait(lock, [this]() { return complete.load(); }); }
|
|
|
|
virtual void Run() = 0;
|
|
};
|
|
|
|
template <typename T>
|
|
class MultiThreading::Task : public TaskBase {
|
|
private:
|
|
T* result = nullptr;
|
|
std::function<T()> callable = nullptr;
|
|
private:
|
|
explicit Task(std::function<T()> callable, std::function<void()> complete_callback = nullptr, T* result = nullptr) : TaskBase(complete_callback), result(result), callable(std::move(callable)) {}
|
|
public:
|
|
void Run() final { result ? *result = callable() : callable(); MarkTaskComplete(); if (complete_callback) complete_callback(); }
|
|
public:
|
|
/// Create a Task. This is non-negotiable, This ensures that there's no issues related to the stack frame the task is on being popped before or while the job runs.
|
|
/// @param callable The function to run, *usually a lambda or std::bind*
|
|
/// @param complete_callback A void callable to be run after the task.
|
|
/// @param result If your function returns a value, This is where you want it to go. nullptr for no return value or you don't want it.
|
|
/// @note If result is stack allocated your program will probably explode.
|
|
/// @note this is shared_ptr so that you don't have to delete it.
|
|
static std::shared_ptr<Task<T>> Create(std::function<T()> callable, const std::function<void()>& complete_callback = nullptr, T* result = nullptr) { return std::shared_ptr<Task<T>>(new Task<T>(callable, complete_callback, result)); };
|
|
~Task() = default;
|
|
};
|
|
|
|
// Special case for if the task is void return type because templates are weird.
|
|
template <>
|
|
class MultiThreading::Task<void> : public TaskBase {
|
|
private:
|
|
std::function<void()> callable = nullptr;
|
|
explicit Task(std::function<void()> callable, std::function<void()> complete_callback = nullptr) : TaskBase(std::move(complete_callback)), callable(std::move(callable)) {}
|
|
public:
|
|
void Run() final { callable(); MarkTaskComplete(); if (complete_callback) complete_callback(); }
|
|
|
|
/// Create a Task. This is non-negotiable, This ensures that there's no issues related to the stack frame the task is on being popped before or while the job runs.
|
|
/// @param callable The function to run, *usually a lambda or std::bind*
|
|
/// @param complete_callback A void callable to be run after the task.
|
|
/// @note this is shared_ptr so that you don't have to delete it.
|
|
static std::shared_ptr<Task<void>> Create(const std::function<void()>& callable, const std::function<void()>& complete_callback = nullptr) { return std::shared_ptr<Task<void>>(new Task<void>(callable, complete_callback)); }
|
|
~Task() = default;
|
|
}; |