feat: implemented simulation multithreading
This commit is contained in:
parent
34a289d36b
commit
99ec92ba88
|
|
@ -1,10 +1,12 @@
|
||||||
#include "simulation.h"
|
#include "simulation.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
|
#include "thread_pool.h"
|
||||||
#include <SDL3/SDL_log.h>
|
#include <SDL3/SDL_log.h>
|
||||||
#include <SDL3/SDL_render.h>
|
#include <SDL3/SDL_render.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <clay/clay.h>
|
#include <clay/clay.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -73,21 +75,33 @@ static size_t CountNeighbors(Cell const &cell) {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PopulateChanges() {
|
static std::mutex underpopulatedMutex, overpopulatedMutex, bornMutex;
|
||||||
|
|
||||||
|
static void FindOverpopulated() {
|
||||||
|
overpopulatedMutex.lock();
|
||||||
overpopulated.clear();
|
overpopulated.clear();
|
||||||
underpopulated.clear();
|
|
||||||
born.clear();
|
|
||||||
// TODO: consider multithreading, this is highly parallelisable
|
|
||||||
std::ranges::copy_if(living, std::back_inserter(overpopulated),
|
std::ranges::copy_if(living, std::back_inserter(overpopulated),
|
||||||
[&](Cell const &c) -> bool {
|
[&](Cell const &c) -> bool {
|
||||||
size_t const neighbors{ CountNeighbors(c) };
|
size_t const neighbors{ CountNeighbors(c) };
|
||||||
return neighbors > 3;
|
return neighbors > 3;
|
||||||
});
|
});
|
||||||
|
overpopulatedMutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FindUnderpopulated() {
|
||||||
|
underpopulatedMutex.lock();
|
||||||
|
underpopulated.clear();
|
||||||
std::ranges::copy_if(living, std::back_inserter(underpopulated),
|
std::ranges::copy_if(living, std::back_inserter(underpopulated),
|
||||||
[&](Cell const &c) -> bool {
|
[&](Cell const &c) -> bool {
|
||||||
size_t const neighbors{ CountNeighbors(c) };
|
size_t const neighbors{ CountNeighbors(c) };
|
||||||
return neighbors < 2;
|
return neighbors < 2;
|
||||||
});
|
});
|
||||||
|
underpopulatedMutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FindBorn() {
|
||||||
|
bornMutex.lock();
|
||||||
|
born.clear();
|
||||||
for (Cell const &cell : living) {
|
for (Cell const &cell : living) {
|
||||||
std::vector<Cell> neighbors{ NeighborSet(cell) };
|
std::vector<Cell> neighbors{ NeighborSet(cell) };
|
||||||
std::ranges::copy_if(neighbors, std::back_inserter(born),
|
std::ranges::copy_if(neighbors, std::back_inserter(born),
|
||||||
|
|
@ -96,6 +110,19 @@ static void PopulateChanges() {
|
||||||
return !living.contains(c) && neighbors == 3;
|
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) {
|
void InitializeRandom(size_t livingChance, int64_t fillArea) {
|
||||||
|
|
@ -115,15 +142,21 @@ void InitializeRandom(size_t livingChance, int64_t fillArea) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Step() {
|
void Step() {
|
||||||
|
underpopulatedMutex.lock();
|
||||||
for (Cell const &cell : underpopulated) {
|
for (Cell const &cell : underpopulated) {
|
||||||
living.erase(cell);
|
living.erase(cell);
|
||||||
}
|
}
|
||||||
|
underpopulatedMutex.unlock();
|
||||||
|
overpopulatedMutex.lock();
|
||||||
for (Cell const &cell : overpopulated) {
|
for (Cell const &cell : overpopulated) {
|
||||||
living.erase(cell);
|
living.erase(cell);
|
||||||
}
|
}
|
||||||
|
overpopulatedMutex.unlock();
|
||||||
|
bornMutex.lock();
|
||||||
for (Cell const &cell : born) {
|
for (Cell const &cell : born) {
|
||||||
living.insert(cell);
|
living.insert(cell);
|
||||||
}
|
}
|
||||||
|
bornMutex.unlock();
|
||||||
PopulateChanges();
|
PopulateChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,24 +183,30 @@ void Draw(SDL_Renderer *renderer, double cellSizePercent) {
|
||||||
if (!drawDebugInfo) {
|
if (!drawDebugInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
overpopulatedMutex.lock();
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
|
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
|
||||||
for (Cell const &cell : overpopulated) {
|
for (Cell const &cell : overpopulated) {
|
||||||
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
||||||
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
||||||
SDL_RenderRect(renderer, &cellRect);
|
SDL_RenderRect(renderer, &cellRect);
|
||||||
}
|
}
|
||||||
|
overpopulatedMutex.unlock();
|
||||||
|
underpopulatedMutex.lock();
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
|
SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
|
||||||
for (Cell const &cell : underpopulated) {
|
for (Cell const &cell : underpopulated) {
|
||||||
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
||||||
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
||||||
SDL_RenderRect(renderer, &cellRect);
|
SDL_RenderRect(renderer, &cellRect);
|
||||||
}
|
}
|
||||||
|
underpopulatedMutex.unlock();
|
||||||
|
bornMutex.lock();
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
|
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
|
||||||
for (Cell const &cell : born) {
|
for (Cell const &cell : born) {
|
||||||
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
||||||
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
||||||
SDL_RenderRect(renderer, &cellRect);
|
SDL_RenderRect(renderer, &cellRect);
|
||||||
}
|
}
|
||||||
|
bornMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetSimulationHovered(bool value) {
|
void SetSimulationHovered(bool value) {
|
||||||
|
|
|
||||||
56
src/thread_pool.cpp
Normal file
56
src/thread_pool.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#include "thread_pool.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
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<std::mutex> 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{};
|
||||||
|
}
|
||||||
30
src/thread_pool.h
Normal file
30
src/thread_pool.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef THREAD_POOL_H
|
||||||
|
#define THREAD_POOL_H
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace threading {
|
||||||
|
typedef std::function<void()> TaskFunc;
|
||||||
|
|
||||||
|
class ThreadPool {
|
||||||
|
public: ThreadPool();
|
||||||
|
~ThreadPool();
|
||||||
|
void ScheduleTask(TaskFunc jobs);
|
||||||
|
size_t GetThreadCount();
|
||||||
|
private:
|
||||||
|
void ThreadFn();
|
||||||
|
std::queue<TaskFunc> taskQueue{};
|
||||||
|
std::vector<std::thread> threads{};
|
||||||
|
std::mutex lock{};
|
||||||
|
std::condition_variable threadNotifier{};
|
||||||
|
bool shutdown{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ThreadPool tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !THREAD_POOL_H
|
||||||
Loading…
Reference in a new issue