Files
Seele/src/Engine/ThreadPool.h
T

308 lines
7.2 KiB
C++
Raw Normal View History

2021-10-12 14:20:30 +02:00
#pragma once
#include <thread>
2021-11-18 12:48:21 +01:00
#include <coroutine>
2021-11-05 00:01:12 +01:00
#include "MinimalEngine.h"
2021-10-23 00:22:35 +02:00
#include "Containers/List.h"
2021-12-09 12:38:02 +01:00
#include "Concepts.h"
2021-10-12 14:20:30 +02:00
namespace Seele
{
2021-11-01 20:25:16 +01:00
extern class ThreadPool& getGlobalThreadPool();
template<bool MainJob>
2021-12-15 00:05:42 +01:00
struct JobPromiseBase;
2021-11-01 20:25:16 +01:00
struct Event
{
public:
Event();
2021-12-02 13:00:03 +01:00
Event(nullptr_t);
2021-11-01 20:25:16 +01:00
Event(const std::string& name);
2021-11-11 20:12:50 +01:00
auto operator<=>(const Event& other) const
{
2021-11-13 19:28:18 +01:00
return flag <=> other.flag;
2021-11-11 20:12:50 +01:00
}
2021-12-15 00:05:42 +01:00
bool operator==(const Event& other) const
{
return flag == other.flag;
}
2021-11-11 20:12:50 +01:00
Event operator co_await()
{
return *this;
}
operator bool()
{
return flag->load();
}
2021-11-11 20:12:50 +01:00
2021-11-01 20:25:16 +01:00
void raise();
void reset();
bool await_ready();
template<bool MainJob>
constexpr void await_suspend(std::coroutine_handle<JobPromiseBase<MainJob>> h);
constexpr void await_resume() {}
private:
std::string name;
RefPtr<std::atomic_bool> flag;
2021-11-11 20:12:50 +01:00
friend class ThreadPool;
2021-10-12 14:20:30 +02:00
};
2021-12-27 15:04:53 +01:00
extern std::atomic_uint64_t globalCounter;
2021-12-15 00:05:42 +01:00
template<bool MainJob>
struct JobBase;
template<bool MainJob>
struct JobPromiseBase
2021-11-24 12:10:23 +01:00
{
2021-12-27 15:04:53 +01:00
enum class State
{
READY,
WAITING,
SCHEDULED,
EXECUTING,
DONE
};
2021-12-15 00:05:42 +01:00
JobPromiseBase()
2021-12-09 12:38:02 +01:00
{
2021-12-15 00:05:42 +01:00
handle = std::coroutine_handle<JobPromiseBase<MainJob>>::from_promise(*this);
id = globalCounter++;
finishedEvent = Event(std::format("Job {}", id));
2021-12-09 12:38:02 +01:00
}
2021-12-15 00:05:42 +01:00
JobBase<MainJob> get_return_object() noexcept;
inline auto initial_suspend() noexcept;
inline auto final_suspend() const noexcept;
void return_void() noexcept {}
void unhandled_exception() noexcept {
std::cerr << "Unhandled exception" << std::endl;
exit(1);
};
2021-11-24 12:10:23 +01:00
void resume()
{
2021-12-15 00:05:42 +01:00
std::unique_lock lock(promiseLock);
2021-12-27 15:04:53 +01:00
if(!handle || handle.done() || executing())
2021-12-15 00:05:42 +01:00
{
2021-12-27 15:04:53 +01:00
return;
2021-12-15 00:05:42 +01:00
}
2021-12-27 15:04:53 +01:00
state = State::EXECUTING;
handle.resume();
2021-11-24 12:10:23 +01:00
}
2021-12-15 00:05:42 +01:00
void setContinuation(JobPromiseBase* cont)
2021-12-09 12:38:02 +01:00
{
2021-12-15 00:05:42 +01:00
std::unique_lock lock(promiseLock);
std::unique_lock lock2(cont->promiseLock);
continuation = cont->handle;
cont->precondition = finishedEvent;
2021-12-02 13:00:03 +01:00
}
bool done()
{
return handle.done();
}
2021-12-27 15:04:53 +01:00
bool scheduled()
{
return state == State::SCHEDULED;
}
bool waiting()
{
return state == State::WAITING;
}
bool executing()
{
return state == State::EXECUTING;
}
bool ready()
{
return state == State::READY;
}
2021-12-02 13:00:03 +01:00
void raise()
{
2021-12-15 00:05:42 +01:00
std::unique_lock lock(promiseLock);
2021-12-27 15:04:53 +01:00
state = State::DONE;
2021-12-15 00:05:42 +01:00
finishedEvent.raise();
2021-12-02 13:00:03 +01:00
}
void reset()
{
2021-12-15 00:05:42 +01:00
std::unique_lock lock(promiseLock);
finishedEvent.reset();
}
void enqueue(Event& event)
{
2021-12-27 15:04:53 +01:00
// no need to lock here, as it is only called while executing, where the lock is already held
if(!handle || handle.done() || waiting() || scheduled())
2021-12-22 11:42:07 +01:00
{
return;
}
2021-12-27 15:04:53 +01:00
state = State::WAITING;
2021-12-15 00:05:42 +01:00
getGlobalThreadPool().enqueueWaiting(event, this);
}
void schedule()
{
std::unique_lock lock(promiseLock);
2021-12-27 15:04:53 +01:00
if(!handle || done() || !ready())
2021-12-15 00:05:42 +01:00
{
return;
}
2021-12-27 15:04:53 +01:00
state = (precondition == nullptr) ? State::SCHEDULED : State::WAITING;
2021-12-15 00:05:42 +01:00
getGlobalThreadPool().enqueueWaiting(precondition, this);
}
std::mutex promiseLock;
std::coroutine_handle<JobPromiseBase> handle;
std::coroutine_handle<> continuation = std::noop_coroutine();
uint64 id;
Event finishedEvent;
Event precondition = nullptr;
2021-12-27 15:04:53 +01:00
State state = State::READY;
2021-12-15 00:05:42 +01:00
};
template<bool MainJob = false>
struct JobBase
{
public:
using promise_type = JobPromiseBase<MainJob>;
explicit JobBase()
: promise(nullptr)
{
}
explicit JobBase(JobPromiseBase<MainJob>* promise)
: promise(promise)
{
}
~JobBase()
{
if(promise)
{
promise->schedule();
}
}
void resume()
{
promise->resume();
}
template<std::invocable Callable>
2021-12-22 11:42:07 +01:00
inline JobBase&& then(Callable callable)
2021-12-15 00:05:42 +01:00
{
return then(callable());
}
2021-12-22 11:42:07 +01:00
JobBase&& then(JobBase&& continuation)
2021-12-15 00:05:42 +01:00
{
promise->setContinuation(continuation.promise);
2021-12-22 11:42:07 +01:00
return std::move(continuation);
2021-12-15 00:05:42 +01:00
}
bool done()
{
return promise->done();
}
void raise()
{
promise->raise();
}
void reset()
{
promise->reset();
2021-12-02 13:00:03 +01:00
}
Event operator co_await()
{
2021-12-27 15:04:53 +01:00
promise->schedule();
2021-12-15 00:05:42 +01:00
return promise->finishedEvent;
2021-12-02 13:00:03 +01:00
}
2021-12-09 12:38:02 +01:00
static JobBase all() = delete;
2021-12-02 13:00:03 +01:00
template<typename... Awaitable>
static JobBase all(Awaitable... jobs)
{
2021-12-09 12:38:02 +01:00
(co_await jobs, ...);
2021-12-02 13:00:03 +01:00
}
2021-12-09 12:38:02 +01:00
template<iterable Iterable>
2021-12-02 13:00:03 +01:00
static JobBase all(Iterable&& collection)
{
for(auto&& it : collection)
{
co_await it;
}
}
2021-12-09 12:38:02 +01:00
template<std::invocable JobFunc, iterable IterableParams>
2021-12-02 13:00:03 +01:00
static JobBase launchJobs(JobFunc&& func, IterableParams params)
{
List<JobBase> jobs;
for(auto&& param : params)
{
jobs.add(func(param));
}
for(auto job : jobs)
{
co_await job;
}
}
2021-11-24 12:10:23 +01:00
private:
2021-12-15 00:05:42 +01:00
JobPromiseBase<MainJob>* promise;
2021-11-24 12:10:23 +01:00
};
using MainJob = JobBase<true>;
using Job = JobBase<false>;
2021-12-15 00:05:42 +01:00
using MainPromise = JobPromiseBase<true>;
using Promise = JobPromiseBase<false>;
2021-11-24 12:10:23 +01:00
2021-10-12 14:20:30 +02:00
class ThreadPool
{
public:
2021-12-02 13:00:03 +01:00
ThreadPool(uint32 threadCount = std::thread::hardware_concurrency());
2021-10-12 14:20:30 +02:00
virtual ~ThreadPool();
2021-12-15 00:05:42 +01:00
// Adds a job to the waiting queue for event, or directly to the job queue if event is nullptr
void enqueueWaiting(Event& event, Promise* job);
// Adds a job to the waiting queue for event, or directly to the job queue if event is nullptr
void enqueueWaiting(Event& event, MainPromise* job);
2021-11-24 12:10:23 +01:00
void notify(Event& event);
2021-12-02 13:00:03 +01:00
void threadLoop(const bool isMainThread);
2021-10-12 14:20:30 +02:00
private:
2021-11-01 20:25:16 +01:00
std::atomic_bool running;
2021-10-23 00:22:35 +02:00
std::vector<std::thread> workers;
2021-11-01 20:25:16 +01:00
2021-12-15 00:05:42 +01:00
List<MainPromise*> mainJobs;
2021-11-01 20:25:16 +01:00
std::mutex mainJobLock;
2021-11-11 20:12:50 +01:00
std::condition_variable mainJobCV;
2021-11-01 20:25:16 +01:00
2021-12-15 00:05:42 +01:00
List<Promise*> jobQueue;
2021-11-01 20:25:16 +01:00
std::mutex jobQueueLock;
2021-11-11 20:12:50 +01:00
std::condition_variable jobQueueCV;
2021-11-01 20:25:16 +01:00
2021-12-22 11:42:07 +01:00
std::map<Event, List<MainPromise*>> waitingMainJobs;
2021-11-01 20:25:16 +01:00
std::mutex waitingMainLock;
2021-12-22 11:42:07 +01:00
std::map<Event, List<Promise*>> waitingJobs;
2021-12-15 00:05:42 +01:00
std::mutex waitingLock;
};
2021-11-01 20:25:16 +01:00
template<bool MainJob>
2021-12-09 12:38:02 +01:00
inline JobBase<MainJob> JobPromiseBase<MainJob>::get_return_object() noexcept
{
2021-12-15 00:05:42 +01:00
return JobBase<MainJob>(this);
2021-11-01 20:25:16 +01:00
}
template<bool MainJob>
2021-12-09 12:38:02 +01:00
inline auto JobPromiseBase<MainJob>::initial_suspend() noexcept
2021-11-01 20:25:16 +01:00
{
2021-12-15 00:05:42 +01:00
return std::suspend_always{};
2021-11-01 20:25:16 +01:00
}
2021-12-02 13:00:03 +01:00
template<bool MainJob>
inline auto JobPromiseBase<MainJob>::final_suspend() const noexcept
{
struct JobAwaitable
{
constexpr bool await_ready() const noexcept { return false; }
constexpr auto await_suspend(std::coroutine_handle<JobPromiseBase<MainJob>> h) const noexcept
{
return h.promise().continuation;
}
constexpr void await_resume() const noexcept {}
};
return JobAwaitable{};
}
2021-11-01 20:25:16 +01:00
template<bool MainJob>
inline constexpr void Event::await_suspend(std::coroutine_handle<JobPromiseBase<MainJob>> h)
{
2021-12-15 00:05:42 +01:00
h.promise().enqueue(*this);
2021-11-01 20:25:16 +01:00
}
2021-10-12 14:20:30 +02:00
} // namespace Seele