diff --git a/src/simulation.cpp b/src/simulation.cpp index 7da8567..b324acb 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -1,10 +1,12 @@ #include "simulation.h" #include "input.h" +#include "thread_pool.h" #include #include #include #include #include +#include #include #include @@ -73,21 +75,33 @@ static size_t CountNeighbors(Cell const &cell) { return count; } -static void PopulateChanges() { +static std::mutex underpopulatedMutex, overpopulatedMutex, bornMutex; + +static void FindOverpopulated() { + overpopulatedMutex.lock(); overpopulated.clear(); - underpopulated.clear(); - born.clear(); - // TODO: consider multithreading, this is highly parallelisable std::ranges::copy_if(living, std::back_inserter(overpopulated), [&](Cell const &c) -> bool { size_t const neighbors{ CountNeighbors(c) }; return neighbors > 3; }); + overpopulatedMutex.unlock(); +} + +static void FindUnderpopulated() { + underpopulatedMutex.lock(); + underpopulated.clear(); std::ranges::copy_if(living, std::back_inserter(underpopulated), [&](Cell const &c) -> bool { size_t const neighbors{ CountNeighbors(c) }; return neighbors < 2; }); + underpopulatedMutex.unlock(); +} + +static void FindBorn() { + bornMutex.lock(); + born.clear(); for (Cell const &cell : living) { std::vector neighbors{ NeighborSet(cell) }; std::ranges::copy_if(neighbors, std::back_inserter(born), @@ -96,6 +110,19 @@ static void PopulateChanges() { return !living.contains(c) && neighbors == 3; }); } + bornMutex.unlock(); +} + +static void PopulateChanges() { +#if 1 + threading::tasks.ScheduleTask(&FindOverpopulated); + threading::tasks.ScheduleTask(&FindUnderpopulated); + threading::tasks.ScheduleTask(&FindBorn); +#else + FindOverpopulated(); + FindUnderpopulated(); + FindBorn(); +#endif } void InitializeRandom(size_t livingChance, int64_t fillArea) { @@ -115,15 +142,21 @@ void InitializeRandom(size_t livingChance, int64_t fillArea) { } void Step() { + underpopulatedMutex.lock(); for (Cell const &cell : underpopulated) { living.erase(cell); } + underpopulatedMutex.unlock(); + overpopulatedMutex.lock(); for (Cell const &cell : overpopulated) { living.erase(cell); } + overpopulatedMutex.unlock(); + bornMutex.lock(); for (Cell const &cell : born) { living.insert(cell); } + bornMutex.unlock(); PopulateChanges(); } @@ -150,24 +183,30 @@ void Draw(SDL_Renderer *renderer, double cellSizePercent) { if (!drawDebugInfo) { return; } + overpopulatedMutex.lock(); SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); for (Cell const &cell : overpopulated) { cellRect.x = (viewOffset.x + cell.x) * cellRect.w; cellRect.y = (viewOffset.y + cell.y) * cellRect.h; SDL_RenderRect(renderer, &cellRect); } + overpopulatedMutex.unlock(); + underpopulatedMutex.lock(); SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255); for (Cell const &cell : underpopulated) { cellRect.x = (viewOffset.x + cell.x) * cellRect.w; cellRect.y = (viewOffset.y + cell.y) * cellRect.h; SDL_RenderRect(renderer, &cellRect); } + underpopulatedMutex.unlock(); + bornMutex.lock(); SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); for (Cell const &cell : born) { cellRect.x = (viewOffset.x + cell.x) * cellRect.w; cellRect.y = (viewOffset.y + cell.y) * cellRect.h; SDL_RenderRect(renderer, &cellRect); } + bornMutex.unlock(); } void SetSimulationHovered(bool value) { diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp new file mode 100644 index 0000000..619fa00 --- /dev/null +++ b/src/thread_pool.cpp @@ -0,0 +1,56 @@ +#include "thread_pool.h" +#include +#include + +namespace threading { +ThreadPool::ThreadPool() { + size_t const thread_count{ std::max(std::thread::hardware_concurrency() / 2, 1u) }; + this->threads.reserve(thread_count); + for (size_t i{ 0 }; i < thread_count; ++i) { + this->threads.emplace_back(std::bind(&ThreadPool::ThreadFn, this)); + } +} + +ThreadPool::~ThreadPool() { + this->lock.lock(); + this->shutdown = true; + this->threadNotifier.notify_all(); + this->lock.unlock(); + + for (std::thread &thread : this->threads) { + thread.join(); + } +} + +size_t ThreadPool::GetThreadCount() { + return this->threads.size(); +} + +void ThreadPool::ScheduleTask(TaskFunc fn) { + this->lock.lock(); + this->taskQueue.emplace(std::move(fn)); + this->threadNotifier.notify_one(); + this->lock.unlock(); +} + +void ThreadPool::ThreadFn() { + TaskFunc function; + for(;;) { + { + std::unique_lock lock{ this->lock }; + while (!this->shutdown && this->taskQueue.empty()) { + this->threadNotifier.wait(lock); + } + if (this->taskQueue.empty()) { + return; + } + function = std::move(this->taskQueue.front()); + this->taskQueue.pop(); + } + + function.operator()(); + } +} + +ThreadPool tasks{}; +} diff --git a/src/thread_pool.h b/src/thread_pool.h new file mode 100644 index 0000000..eb55962 --- /dev/null +++ b/src/thread_pool.h @@ -0,0 +1,30 @@ +#ifndef THREAD_POOL_H +#define THREAD_POOL_H + +#include +#include +#include +#include +#include + +namespace threading { +typedef std::function TaskFunc; + +class ThreadPool { +public: ThreadPool(); + ~ThreadPool(); + void ScheduleTask(TaskFunc jobs); + size_t GetThreadCount(); +private: + void ThreadFn(); + std::queue taskQueue{}; + std::vector threads{}; + std::mutex lock{}; + std::condition_variable threadNotifier{}; + bool shutdown{ false }; +}; + +extern ThreadPool tasks; +} + +#endif // !THREAD_POOL_H