Adding ThreadPool (doesnt work)

This commit is contained in:
Dynamitos
2023-12-17 16:16:46 +01:00
parent c409a3acd0
commit 34e0d283e8
15 changed files with 82 additions and 661 deletions
-2
View File
@@ -39,7 +39,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_CURRENT_SOURCE_DIR}/bi
find_package(Vulkan REQUIRED)
find_package(assimp CONFIG REQUIRED)
find_package(Stb REQUIRED)
find_package(thread-pool CONFIG REQUIRED)
find_package(EnTT CONFIG REQUIRED)
find_package(FreeType CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
@@ -67,7 +66,6 @@ target_link_libraries(Engine PUBLIC freetype)
target_link_libraries(Engine PUBLIC assimp::assimp)
target_link_libraries(Engine PUBLIC KTX::ktx)
target_link_libraries(Engine PUBLIC nlohmann_json::nlohmann_json)
target_link_libraries(Engine PUBLIC dp::thread-pool)
target_link_libraries(Engine PUBLIC crcpp)
target_link_libraries(Engine PUBLIC shader-slang)
if(UNIX)
+2 -2
View File
@@ -1,5 +1,5 @@
#pragma once
#include <thread_pool/thread_pool.h>
#include "ThreadPool.h"
#include "Window/View.h"
#include "Graphics/RenderPass/DepthPrepass.h"
#include "Graphics/RenderPass/LightCullingPass.h"
@@ -34,7 +34,7 @@ private:
LightCullingPass,
BasePass> renderGraph;
dp::thread_pool<> pool;
ThreadPool pool;
ViewportControl cameraSystem;
virtual void keyCallback(KeyCode code, InputAction action, KeyModifier modifier) override;
+5 -2
View File
@@ -3,7 +3,9 @@ target_sources(Engine
Concepts.h
EngineTypes.h
Game.h
MinimalEngine.h)
MinimalEngine.h
ThreadPool.h
ThreadPool.cpp)
target_sources(Engine
PUBLIC FILE_SET HEADERS
@@ -11,7 +13,8 @@ target_sources(Engine
Concepts.h
Game.h
EngineTypes.h
MinimalEngine.h)
MinimalEngine.h
ThreadPool.h)
add_subdirectory(Actor/)
add_subdirectory(Asset/)
+2 -1
View File
@@ -179,6 +179,7 @@ public:
, _size(std::move(other._size))
, allocator(std::move(other.allocator))
{
other._size = 0;
}
List(List&& other, const Allocator& alloc)
: root(std::move(other.root))
@@ -188,7 +189,7 @@ public:
, _size(std::move(other._size))
, allocator(allocator)
{
other.clear();
other._size = 0;
}
~List()
{
+4 -1
View File
@@ -14,6 +14,7 @@ constexpr static uint64 NUM_DEFAULT_ELEMENTS = 1024 * 1024;
void VertexData::resetMeshData()
{
std::unique_lock l(materialDataLock);
for (auto& [_, mat] : materialData)
{
mat.material->getDescriptorLayout()->reset();
@@ -28,11 +29,12 @@ void VertexData::resetMeshData()
void VertexData::updateMesh(PMesh mesh, Component::Transform& transform)
{
std::unique_lock l(materialDataLock);
PMaterial mat = mesh->referencedMaterial->getHandle()->getBaseMaterial();
MaterialData& matData = materialData[mat->getName()];
matData.material = mat;
MaterialInstanceData& matInstanceData = matData.instances[mesh->referencedMaterial->getHandle()->getId()];
for (auto& data : meshData[mesh->id])
for (const auto& data : meshData[mesh->id])
{
matInstanceData.meshes.add(MeshInstanceData{
.instance = InstanceData {
@@ -46,6 +48,7 @@ void VertexData::updateMesh(PMesh mesh, Component::Transform& transform)
void VertexData::createDescriptors()
{
std::unique_lock l(materialDataLock);
instanceDataLayout->reset();
for (const auto& [_, mat] : materialData)
{
+1
View File
@@ -100,6 +100,7 @@ protected:
uint32_t vertexOffset;
uint32_t primitiveOffset;
};
std::mutex materialDataLock;
Map<std::string, MaterialData> materialData;
Map<MeshId, Array<MeshData>> meshData;
Map<MeshId, uint64> meshOffsets;
+11 -7
View File
@@ -28,25 +28,29 @@ public:
{
return (deps | ...);
}
void setupView(Dependencies<>, dp::thread_pool<>&)
void setupView(Dependencies<>, ThreadPool& pool)
{
List<std::function<void()>> work;
registry.view<Components...>().each([&](Components&... comp){
//pool.enqueue_detach([&](){
work.add([&](){
update(comp...);
//});
});
});
pool.runAndWait(std::move(work));
}
template<typename... Deps>
void setupView(Dependencies<Deps...>, dp::thread_pool<>&)
void setupView(Dependencies<Deps...>, ThreadPool& pool)
{
List<std::function<void()>> work;
registry.view<Components..., Deps...>().each([&](Components&... comp, Deps&... deps){
//pool.enqueue_detach([&](){
work.add([&]() {
(accessComponent(deps) + ...);
update((comp,...));
//});
});
});
pool.runAndWait(std::move(work));
}
virtual void run(dp::thread_pool<>& pool, double delta) override
virtual void run(ThreadPool& pool, double delta) override
{
SystemBase::run(pool, delta);
setupView(mergeDependencies((getDependencies<Components>(),...)), pool);
+1 -2
View File
@@ -1,7 +1,6 @@
#pragma once
#include "MinimalEngine.h"
#include "SystemBase.h"
#include <thread_pool/thread_pool.h>
namespace Seele
{
@@ -13,7 +12,7 @@ public:
Executor();
~Executor();
private:
dp::thread_pool<> pool;
ThreadPool pool;
};
} // namespace System
} // namespace Seele
+2 -2
View File
@@ -1,6 +1,6 @@
#pragma once
#include <thread_pool/thread_pool.h>
#include <entt/entt.hpp>
#include "ThreadPool.h"
#include "Scene/Scene.h"
namespace Seele
@@ -12,7 +12,7 @@ class SystemBase
public:
SystemBase(PScene scene) : registry(scene->registry), scene(scene) {}
virtual ~SystemBase() {}
virtual void run(dp::thread_pool<>&, double delta)
virtual void run(ThreadPool& pool, double delta)
{
deltaTime = delta;
update();
+1 -1
View File
@@ -7,7 +7,7 @@ void SystemGraph::addSystem(System::OSystemBase system)
systems.add(std::move(system));
}
void SystemGraph::run(dp::thread_pool<>& threadPool, float deltaTime)
void SystemGraph::run(ThreadPool& threadPool, float deltaTime)
{
for(auto& system : systems) {
system->run(threadPool, deltaTime);
+2 -1
View File
@@ -1,4 +1,5 @@
#pragma once
#include "ThreadPool.h"
#include "Containers/Array.h"
#include "SystemBase.h"
@@ -8,7 +9,7 @@ class SystemGraph
{
public:
void addSystem(System::OSystemBase system);
void run(dp::thread_pool<>& threadPool, float deltaTime);
void run(ThreadPool& threadPool, float deltaTime);
private:
Array<System::OSystemBase> systems;
};
+33 -191
View File
@@ -1,224 +1,66 @@
#include "ThreadPool.h"
#include <memory_resource>
using namespace Seele;
std::mutex Seele::promisesLock;
List<Promise*> Seele::promises;
//Event::Event(nullptr_t)
//{
//}
Event::Event(const std::string &name, const std::source_location &location)
: state(std::make_shared<EventState>())
ThreadPool::ThreadPool(uint32 numWorkers)
{
state->name = name;
state->location = location;
}
Event::Event(const std::source_location &location)
: state(std::make_shared<EventState>())
{
state->name = location.function_name();
state->location = location;
}
Event::Event(const Event& other)
{
std::scoped_lock lock(other.eventLock);
state = other.state;
}
Event::Event(Event&& other)
{
std::scoped_lock lock(other.eventLock);
state = std::move(other.state);
}
Event& Event::operator=(const Event& other)
{
if(this != &other)
for (uint32 i = 0; i < numWorkers; ++i)
{
std::scoped_lock lock(eventLock, other.eventLock);
state = other.state;
}
return *this;
}
Event& Event::operator=(Event&& other)
{
if(this != &other)
{
std::scoped_lock lock(eventLock, other.eventLock);
state = std::move(other.state);
}
return *this;
}
void Event::raise()
{
std::scoped_lock lock(eventLock);
state->data = true;
if(state->waitingJobs.size() > 0)
{
getGlobalThreadPool().scheduleBatch(state->waitingJobs);
state->waitingJobs.clear();
}
if(state->waitingMainJobs.size() > 0)
{
getGlobalThreadPool().scheduleBatch(state->waitingMainJobs);
state->waitingMainJobs.clear();
}
}
void Event::reset()
{
std::scoped_lock lock(eventLock);
state->data = false;
}
bool Event::await_ready()
{
eventLock.lock();
bool result = state->data;
if(result)
{
eventLock.unlock();
}
return result;
}
void Event::await_suspend(std::coroutine_handle<JobPromiseBase<false>> h)
{
state->waitingJobs.add(JobBase<false>(&h.promise()));
eventLock.unlock();
}
void Event::await_suspend(std::coroutine_handle<JobPromiseBase<true>> h)
{
state->waitingMainJobs.add(JobBase<true>(&h.promise()));
eventLock.unlock();
}
ThreadPool::ThreadPool(uint32 threadCount)
: workers(threadCount)
{
running.store(true);
for (uint32 i = 0; i < threadCount; ++i)
{
workers[i] = std::thread(&ThreadPool::threadLoop, this);
workers.add(std::thread(&ThreadPool::work, this));
}
}
ThreadPool::~ThreadPool()
{
running.store(false);
{
std::unique_lock lock(mainJobLock);
mainJobCV.notify_all();
std::unique_lock l(taskLock);
running = false;
taskCV.notify_all();
}
{
std::unique_lock lock(jobQueueLock);
jobQueueCV.notify_all();
}
for(auto& worker : workers)
for (auto& worker : workers)
{
worker.join();
}
}
void ThreadPool::waitIdle()
void ThreadPool::runAndWait(List<std::function<void()>> functions)
{
while(true)
{
std::unique_lock lock(numIdlingLock);
if(numIdling == workers.size())
{
assert(promises.size() == 0);
return;
}
numIdlingIncr.wait(lock);
std::unique_lock l(taskLock);
currentTask.numRemaining = functions.size();
currentTask.functions = std::move(functions);
taskCV.notify_all();
}
}
void ThreadPool::scheduleJob(Job job)
{
assert(!job.done());
std::scoped_lock lock(jobQueueLock);
jobQueue.add(std::move(job));
jobQueueCV.notify_one();
}
void ThreadPool::scheduleJob(MainJob job)
{
assert(!job.done());
std::scoped_lock lock(mainJobLock);
mainJobs.add(std::move(job));
mainJobCV.notify_one();
}
void ThreadPool::mainLoop()
{
while(running.load())
while (currentTask.numRemaining > 0)
{
MainJob job;
{
std::unique_lock lock(mainJobLock);
if(mainJobs.empty())
{
mainJobCV.wait(lock);
}
[[likely]]
if(!mainJobs.empty())
{
job = mainJobs.front();
mainJobs.popFront();
}
else
{
continue;
}
}
job.resume();
std::unique_lock l2(completedLock);
completedCV.wait(l2);
}
}
void ThreadPool::threadLoop()
void ThreadPool::work()
{
List<Job> localQueue;
while (running.load())
while (true)
{
[[likely]]
if(!localQueue.empty())
std::unique_lock l(taskLock);
while(currentTask.functions.empty())
{
Job job = localQueue.retrieve();
job.resume();
taskCV.wait(l);
if (!running)
{
return;
}
}
else
auto func = std::move(currentTask.functions.front());
currentTask.functions.popFront();
l.unlock();
func();
l.lock();
currentTask.numRemaining--;
if (currentTask.numRemaining == 0)
{
std::unique_lock lock(jobQueueLock);
if (jobQueue.empty())
{
{
std::unique_lock lock2(numIdlingLock);
numIdling++;
numIdlingIncr.notify_one();
}
jobQueueCV.wait(lock);
{
std::unique_lock lock2(numIdlingLock);
numIdling--;
}
}
// take 1/numThreads jobs, maybe make this a parameter that
// adjusts based on past workload
uint32 partitionedWorkload = (uint32)(jobQueue.size() / workers.size());
uint32 numTaken = std::clamp(partitionedWorkload, 1u, localQueueSize);
while (!jobQueue.empty() && localQueue.size() < numTaken)
{
localQueue.add(jobQueue.retrieve());
}
std::unique_lock l2(completedLock);
completedCV.notify_one();
}
}
}
ThreadPool &Seele::getGlobalThreadPool()
{
static ThreadPool threadPool;
return threadPool;
}
+17 -447
View File
@@ -1,459 +1,29 @@
#pragma once
#include <thread>
#include <coroutine>
#include "MinimalEngine.h"
#include "Containers/Array.h"
#include "Containers/List.h"
#include "Concepts.h"
#include <source_location>
namespace Seele
{
extern class ThreadPool& getGlobalThreadPool();
template<bool MainJob>
struct JobBase;
template<bool MainJob>
struct JobPromiseBase;
struct Event
{
public:
//Event(nullptr_t);
Event(const std::string& name, const std::source_location& location = std::source_location::current());
Event(const std::source_location& location = std::source_location::current());
Event(const Event& other);
Event(Event&& other);
~Event() = default;
Event& operator=(const Event& other);
Event& operator=(Event&& other);
auto operator<=>(const Event& other) const
{
return state->name <=> other.state->name;
}
bool operator==(const Event& other) const
{
return state->name == other.state->name;
}
operator bool()
{
std::scoped_lock lock(eventLock);
return state->data;
}
friend std::ostream& operator<<(std::ostream& stream, const Event& event)
{
stream
<< event.state->location.file_name()
<< "("
<< event.state->location.line()
<< ":"
<< event.state->location.column()
<< "): "
<< event.state->location.function_name();
return stream;
}
void raise();
void reset();
bool await_ready();
void await_suspend(std::coroutine_handle<JobPromiseBase<false>> h);
void await_suspend(std::coroutine_handle<JobPromiseBase<true>> h);
constexpr void await_resume() {}
private:
mutable std::mutex eventLock;
struct EventState
{
std::string name;
std::source_location location;
bool data = false;
Array<JobBase<false>> waitingJobs;
Array<JobBase<true>> waitingMainJobs;
};
std::shared_ptr<EventState> state;
friend class ThreadPool;
};
extern std::mutex promisesLock;
extern List<JobPromiseBase<false>*> promises;
template<bool MainJob>
struct JobPromiseBase
{
enum class State
{
READY,
WAITING,
SCHEDULED,
EXECUTING,
DONE
};
JobPromiseBase(const std::source_location& location = std::source_location::current())
: pad0(12345)//0x7472617453)
, handle(std::coroutine_handle<JobPromiseBase<MainJob>>::from_promise(*this))
, waitingFor(nullptr)
, continuation(nullptr)
, numRefs(0)
, finishedEvent(location)
, state(State::READY)
, pad1(12345)//0x646E45)
{
if constexpr(!MainJob)
{
std::scoped_lock lock(promisesLock);
promises.add(this);
}
}
~JobPromiseBase()
{
if constexpr (!MainJob)
{
std::scoped_lock lock(promisesLock);
promises.remove(promises.find(this));
}
}
JobBase<MainJob> get_return_object() noexcept;
inline auto initial_suspend() noexcept;
inline auto final_suspend() noexcept;
void return_void() noexcept {}
void unhandled_exception() {
std::exception_ptr exception = std::current_exception();
std::cerr << "Unhandled exception!" << std::endl;
throw exception;
};
void resume()
{
std::scoped_lock lock(promiseLock);
validate();
if(!handle || handle.done() || executing())
{
return;
}
state = State::EXECUTING;
handle.resume();
}
void setContinuation(JobPromiseBase* cont)
{
std::scoped_lock lock(promiseLock, cont->promiseLock);
validate();
assert(cont->ready());
continuation = cont;
cont->state = State::SCHEDULED;
cont->waitingFor = &finishedEvent;
cont->addRef();
}
bool done()
{
validate();
return state == State::DONE;
}
bool scheduled()
{
validate();
return state == State::SCHEDULED;
}
bool waiting()
{
validate();
return state == State::WAITING;
}
bool executing()
{
validate();
return state == State::EXECUTING;
}
bool ready()
{
validate();
return state == State::READY;
}
void enqueue(Event* event);
bool schedule();
void addRef()
{
validate();
numRefs++;
}
void removeRef()
{
{
std::scoped_lock lock(promiseLock);
validate();
numRefs--;
if(numRefs != 0)
{
return;
}
if(schedule())
{
return;
}
}
handle.destroy();
}
void validate()
{
assert(pad0 == 12345);
assert(pad1 == 12345);
}
uint64 pad0;
std::mutex promiseLock;
std::coroutine_handle<JobPromiseBase> handle;
Event* waitingFor;
JobPromiseBase* continuation;
std::atomic_uint64_t numRefs;
Event finishedEvent;
State state;
uint64 pad1;
};
template<bool MainJob = false>
struct JobBase
{
public:
using promise_type = JobPromiseBase<MainJob>;
explicit JobBase()
: promise(nullptr)
{
}
explicit JobBase(JobPromiseBase<MainJob>* promise)
: promise(promise)
{
promise->addRef();
}
JobBase(const JobBase& other)
{
promise = other.promise;
if(promise != nullptr)
{
promise->addRef();
}
}
JobBase(JobBase&& other)
{
promise = other.promise;
other.promise = nullptr;
}
~JobBase()
{
if(promise)
{
//promise->schedule();
promise->removeRef();
}
}
JobBase& operator=(const JobBase& other)
{
if(this != &other)
{
if(promise != nullptr)
{
promise->removeRef();
}
promise = other.promise;
if(promise != nullptr)
{
promise->addRef();
}
}
return *this;
}
JobBase& operator=(JobBase&& other)
{
if(this != &other)
{
if(promise != nullptr)
{
promise->removeRef();
}
promise = other.promise;
other.promise = nullptr;
}
return *this;
}
void resume()
{
promise->resume();
}
template<std::invocable Callable>
inline JobBase then(Callable callable)
{
return then(callable());
}
JobBase then(JobBase continuation)
{
promise->setContinuation(continuation.promise);
continuation.promise->state = JobPromiseBase<MainJob>::State::SCHEDULED;
return continuation;
}
bool done()
{
return promise->done();
}
Event& operator co_await()
{
// the co_await operator keeps a reference to this, it won't
// be scheduled from the destructor
std::scoped_lock lock(promise->promiseLock);
promise->schedule();
return promise->finishedEvent;
}
static JobBase all() = delete;
template<std::ranges::range Iterable>
requires std::same_as<std::ranges::range_value_t<Iterable>, JobBase<MainJob>>
static JobBase<MainJob> all(Iterable collection);
template<std::ranges::range Iterable>
static JobBase all(Iterable collection)
{
for(auto it : collection)
{
co_await it;
}
}
template<typename... Awaitable>
static JobBase all(Awaitable... jobs)
{
std::vector vec{jobs...};
return std::move(JobBase::all(std::move(vec)));
}
template<typename JobFunc, std::ranges::input_range Iterable>
requires std::invocable<JobFunc, std::ranges::range_reference_t<Iterable>>
static JobBase launchJobs(JobFunc&& func, Iterable params);
private:
JobPromiseBase<MainJob>* promise;
friend class ThreadPool;
};
using MainJob = JobBase<true>;
using Job = JobBase<false>;
using MainPromise = JobPromiseBase<true>;
using Promise = JobPromiseBase<false>;
class ThreadPool
{
public:
ThreadPool(uint32 threadCount = std::thread::hardware_concurrency());
virtual ~ThreadPool();
void waitIdle();
void scheduleJob(Job job);
void scheduleJob(MainJob job);
template<std::ranges::range Iterable>
requires std::same_as<std::ranges::range_value_t<Iterable>, MainJob>
void scheduleBatch(Iterable jobs)
{
std::scoped_lock lock(mainJobLock);
for(auto job : jobs)
{
std::scoped_lock l(job.promise->promiseLock);
job.promise->validate();
job.promise->state = JobPromiseBase<true>::State::SCHEDULED;
mainJobs.add(job);
}
mainJobCV.notify_one();
}
template<std::ranges::range Iterable>
requires std::same_as<std::ranges::range_value_t<Iterable>, Job>
void scheduleBatch(Iterable jobs)
{
std::scoped_lock lock(jobQueueLock);
for(auto job : jobs)
{
std::scoped_lock l(job.promise->promiseLock);
job.promise->validate();
job.promise->state = JobPromiseBase<false>::State::SCHEDULED;
jobQueue.add(job);
}
jobQueueCV.notify_all();
}
void notify(Event* event);
void mainLoop();
void threadLoop();
ThreadPool(uint32 numWorkers = std::thread::hardware_concurrency());
~ThreadPool();
void runAndWait(List<std::function<void()>> functions);
private:
std::atomic_bool running;
std::mutex numIdlingLock;
std::condition_variable numIdlingIncr;
uint32 numIdling;
struct Task
{
uint64 numRemaining = 0;
List<std::function<void()>> functions;
};
void work();
Array<std::thread> workers;
List<MainJob> mainJobs;
std::mutex mainJobLock;
std::condition_variable mainJobCV;
List<Job> jobQueue;
std::mutex jobQueueLock;
std::condition_variable jobQueueCV;
uint32 localQueueSize = 50;
std::mutex taskLock;
std::condition_variable taskCV;
std::mutex completedLock;
std::condition_variable completedCV;
Task currentTask;
bool running = true;
};
template<bool MainJob>
inline JobBase<MainJob> JobPromiseBase<MainJob>::get_return_object() noexcept
{
return JobBase<MainJob>(this);
}
template<bool MainJob>
inline auto JobPromiseBase<MainJob>::initial_suspend() noexcept
{
validate();
return std::suspend_always{};
}
template<bool MainJob>
inline auto JobPromiseBase<MainJob>::final_suspend() noexcept
{
validate();
state = State::DONE;
finishedEvent.raise();
if(continuation)
{
getGlobalThreadPool().scheduleJob(JobBase<MainJob>(continuation));
continuation->removeRef();
}
return std::suspend_always{};
}
template<bool MainJob>
inline bool JobPromiseBase<MainJob>::schedule()
{
validate();
if(!handle || done() || !ready())
{
return false;
}
state = State::SCHEDULED;
getGlobalThreadPool().scheduleJob(std::move(JobBase<MainJob>(this)));
return true;
}
template<bool MainJob>
template<std::ranges::range Iterable>
requires std::same_as<std::ranges::range_value_t<Iterable>, JobBase<MainJob>>
inline JobBase<MainJob> JobBase<MainJob>::all(Iterable collection)
{
getGlobalThreadPool().scheduleBatch(collection);
for(auto it : collection)
{
co_await it;
}
collection.clear();
}
template<bool MainJob>
template<typename JobFunc, std::ranges::input_range Iterable>
requires std::invocable<JobFunc, std::ranges::range_reference_t<Iterable>>
inline JobBase<MainJob> JobBase<MainJob>::launchJobs(JobFunc&& func, Iterable params)
{
Array<JobBase<MainJob>> jobs;
for(auto&& param : params)
{
jobs.add(func(param));
}
getGlobalThreadPool().scheduleBatch(jobs);
for(auto job : jobs)
{
co_await job;
}
}
} // namespace Seele
}
+1 -1
View File
@@ -40,7 +40,7 @@ private:
PSystemGraph systemGraph;
System::PKeyboardInput keyboardSystem;
dp::thread_pool<> threadPool;
ThreadPool threadPool;
float updateTime = 0;
virtual void keyCallback(Seele::KeyCode code, Seele::InputAction action, Seele::KeyModifier modifier) override;
-1
View File
@@ -3,7 +3,6 @@
"assimp",
"entt",
"stb",
"dp-thread-pool",
"entt",
"freetype",
"glfw3",