Compare commits
10 commits
8538c91206
...
a3396bb6af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3396bb6af | ||
|
|
0f80dc8ca6 | ||
|
|
768c467572 | ||
|
|
577f7c953b | ||
|
|
f20139d0fa | ||
|
|
19f21e5867 | ||
|
|
e5edc2e8f7 | ||
|
|
5484039e3e | ||
|
|
658b96b201 | ||
|
|
74e6456343 |
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.21)
|
||||
project(GameOfLife)
|
||||
project(CHANGEME)
|
||||
|
||||
set(CMAKE_BINARY_DIR "${CMAKE_SOURCE_DIR}/bin")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
|
|
@ -15,11 +15,11 @@ add_subdirectory(vendor/SDL3/ EXCLUDE_FROM_ALL)
|
|||
set(SDLTTF_VENDORED ON)
|
||||
add_subdirectory(vendor/SDL3_ttf/ EXCLUDE_FROM_ALL)
|
||||
|
||||
add_executable(GameOfLife ${source_files})
|
||||
target_link_libraries(GameOfLife PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3)
|
||||
add_executable(CHANGEME ${source_files})
|
||||
target_link_libraries(CHANGEME PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3)
|
||||
|
||||
add_custom_target(copy_assets
|
||||
COMMAND ${CMAKE_COMMAND} -E
|
||||
copy_directory ${CMAKE_SOURCE_DIR}/assets/ ${CMAKE_BINARY_DIR}/assets
|
||||
)
|
||||
add_dependencies(GameOfLife copy_assets)
|
||||
add_dependencies(CHANGEME copy_assets)
|
||||
|
|
|
|||
119
README.md
Normal file
119
README.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Clay SDL3 Template
|
||||
|
||||
## Using Template
|
||||
|
||||
### Just use [`just`](https://just.systems/man/en/)
|
||||
|
||||
Run `just set-project-name {project name}` first.
|
||||
|
||||
And remember to `git remote set-url origin {repo}`.
|
||||
|
||||
### Otherwise
|
||||
|
||||
Modify the CMakeLists.txt manually, just replace CHANGEME with whatever your binary should be called.
|
||||
|
||||
## Compiling
|
||||
|
||||
> Remember to `git submodule update --init --recursive`!
|
||||
|
||||
### Just again
|
||||
|
||||
`just configure` runs cmake configuration and generates a `compile_commands.json`.
|
||||
|
||||
`just build` runs the cmake build.
|
||||
|
||||
### CMake works too, I guess
|
||||
|
||||
`cmake -S. -Bbuild`
|
||||
|
||||
`cmake --build build`
|
||||
|
||||
Same as always
|
||||
|
||||
## Files
|
||||
|
||||
### thread_pool.h/cpp
|
||||
|
||||
Thread pool used for parallel GoL generation ticking.
|
||||
|
||||
### application.h/cpp
|
||||
|
||||
UI and layout logic.
|
||||
|
||||
### style.h/cpp
|
||||
|
||||
Reusable UI styling
|
||||
|
||||
### resources.h/cpp
|
||||
|
||||
Font loading.
|
||||
|
||||
### elements.h/cpp
|
||||
|
||||
Reusable UI elements
|
||||
|
||||
### input.h/cpp
|
||||
|
||||
Handling of input events.
|
||||
|
||||
### main.cpp:
|
||||
|
||||
Entrypoint, setup, and main application loop.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* CMake 3.21 or higher
|
||||
|
||||
* Compiler capable of C++23 and C23.
|
||||
|
||||
## Dependencies
|
||||
|
||||
### SDL3
|
||||
|
||||
Included as git submodule at `vendor/SDL3/` and dynamically linked.
|
||||
|
||||
### SDL3_ttf
|
||||
|
||||
Included as git submodule at `vendor/SDL3_ttf/` and dynamically linked.
|
||||
|
||||
### Clay
|
||||
|
||||
Included as files in `vendor/clay/clay.h`; Single header library.
|
||||
|
||||
### Clay SDL3 renderer
|
||||
|
||||
Included as files in `vendor/renderer/` and compiled as part of the project.
|
||||
|
||||
> Note: Mildly modified from the official Clay_SDL3_renderer to enable more advanced styling.
|
||||
|
||||
## Code Standards
|
||||
|
||||
* Keep program structure as simple as possible. No `class Application {` class or other Java-isms. Prefer namespaces with static lifetime variables.
|
||||
|
||||
* Use STL where possible. Don't reinvent the wheel.
|
||||
|
||||
* K&R brackets. With notable exceptions(1)
|
||||
|
||||
* camelCase for variables, PascalCase for types and functions (that's what SDL and Clay do).
|
||||
|
||||
* In class member functions, always use `this->` to access member variables.
|
||||
|
||||
* const applies to the name to it's left, so it goes after the type, not `const int x;` but `int const x`.
|
||||
|
||||
* \* and & flush with the declaration name. `Type const &Function(Type &inRef)`
|
||||
|
||||
> (1) Bracket exceptions:
|
||||
> * using scoped_lock in arbitrary blocks, prefer tailed lisp brackets
|
||||
|
||||
```
|
||||
struct Data {
|
||||
int x{ 0 }, y{ 0 };
|
||||
};
|
||||
void MyFunction(Data const &data) { // K&R here
|
||||
DoAsynchronousThings();
|
||||
{ scoped_lock lock{ myMutex };
|
||||
myVariable++;
|
||||
} // not quite lisp, lisp with a tail
|
||||
DoMoreAsynchronousThings();
|
||||
}
|
||||
```
|
||||
11
justfile
11
justfile
|
|
@ -1,14 +1,19 @@
|
|||
build:
|
||||
# BUILDING
|
||||
bear -- cmake --build build
|
||||
cmake --build build
|
||||
|
||||
run:
|
||||
cd bin/ && DiceGui
|
||||
cd bin/ && CHANGEME
|
||||
|
||||
configure:
|
||||
# CONFIGURING WITH PREMAKE
|
||||
cmake -S. -Bbuild
|
||||
cmake -S. -Bbuild -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||
|
||||
clean:
|
||||
# CLEANING BUILD ARTEFACTS
|
||||
rm -r bin/**
|
||||
rm -r build/**
|
||||
|
||||
set-project-name projectname: clean
|
||||
git remote set-url origin ""
|
||||
sed -i "s/CHANGEME/{{projectname}}/g" ./CMakeLists.txt ./justfile
|
||||
|
|
|
|||
|
|
@ -1,172 +1,24 @@
|
|||
#include "application.h"
|
||||
#include "style.h"
|
||||
#include "elements.h"
|
||||
#include "simulation.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <clay/clay.h>
|
||||
|
||||
namespace application {
|
||||
static bool isSimulating{ false };
|
||||
static bool lockFramerate{ true };
|
||||
|
||||
static void SetSimulatingButton(Clay_ElementId element, Clay_PointerData pointer, intptr_t data) {
|
||||
if (pointer.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
|
||||
isSimulating = data == 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void RandomizeFieldButton(Clay_ElementId element, Clay_PointerData pointer, intptr_t data) {
|
||||
if (pointer.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
|
||||
simulation::InitializeRandom(2, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void StepSimulationButton(Clay_ElementId element, Clay_PointerData pointer, intptr_t data) {
|
||||
if (pointer.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
|
||||
simulation::Step();
|
||||
}
|
||||
}
|
||||
|
||||
static void StepControl() {
|
||||
CLAY_AUTO_ID(style::PanelContainer(1, {
|
||||
.layout = {
|
||||
.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT() },
|
||||
.childGap = 16,
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
},
|
||||
})) {
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.childGap = 16
|
||||
}
|
||||
}) {
|
||||
if (isSimulating) {
|
||||
elements::TextButton(CLAY_STRING("Pause"), style::warningButton, &SetSimulatingButton, false);
|
||||
} else {
|
||||
elements::TextButton(CLAY_STRING("Start"), style::proceedButton, &SetSimulatingButton, true);
|
||||
elements::TextButton(CLAY_STRING("Step"), style::actionButton, &StepSimulationButton);
|
||||
}
|
||||
}
|
||||
elements::Toggle(CLAY_STRING("Show Changes"), style::actionButton, simulation::drawDebugInfo);
|
||||
elements::Toggle(CLAY_STRING("Lock Speed"), style::actionButton, lockFramerate);
|
||||
}
|
||||
}
|
||||
|
||||
static void DebugInfoLegend() {
|
||||
Clay_ElementDeclaration style{style::PanelContainer(1, {
|
||||
.layout = {
|
||||
.padding = {
|
||||
.left = 20, .right = 0, .top = 0, .bottom = 0
|
||||
},
|
||||
.childGap = 10,
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
}
|
||||
})
|
||||
};
|
||||
style.cornerRadius = { 0, style::defaultRadius, 0, style::defaultRadius };
|
||||
CLAY_AUTO_ID(style) {
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.childGap = 10,
|
||||
},
|
||||
}) {
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.sizing = { CLAY_SIZING_FIXED(20), CLAY_SIZING_FIXED(20) }
|
||||
},
|
||||
.border = { {255, 0, 255, 255 }, CLAY_BORDER_OUTSIDE(3) }
|
||||
});
|
||||
elements::Body(CLAY_STRING("Underpopulated"), {
|
||||
.textColor = style::TextColor(0)
|
||||
});
|
||||
}
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.childGap = 10,
|
||||
}
|
||||
}) {
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.sizing = { CLAY_SIZING_FIXED(20), CLAY_SIZING_FIXED(20) }
|
||||
},
|
||||
.border = { {255, 0, 0, 255 }, CLAY_BORDER_OUTSIDE(3) }
|
||||
});
|
||||
elements::Body(CLAY_STRING("Overpopulated"), {
|
||||
.textColor = style::TextColor(0)
|
||||
});
|
||||
}
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.childGap = 10,
|
||||
}
|
||||
}) {
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.sizing = { CLAY_SIZING_FIXED(20), CLAY_SIZING_FIXED(20) }
|
||||
},
|
||||
.border = { {0, 255, 0, 255 }, CLAY_BORDER_OUTSIDE(3) }
|
||||
});
|
||||
elements::Body(CLAY_STRING("Born"), {
|
||||
.textColor = style::TextColor(0)
|
||||
});
|
||||
}
|
||||
elements::Body(CLAY_STRING("Warning:\nDrawing simulation changes\ngreatly lowers performance."), {
|
||||
.textColor = style::TextColor(2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void PrimaryControls() {
|
||||
static bool isOpen{ true };
|
||||
if (isOpen) {
|
||||
CLAY_AUTO_ID(style::LeftPanelContainer(0, {
|
||||
.layout = {
|
||||
.sizing = { CLAY_SIZING_FIT(.18) },
|
||||
}
|
||||
})) {
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.childGap = 16,
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
},
|
||||
.clip = {
|
||||
true, true, Clay_GetScrollOffset()
|
||||
}
|
||||
}) {
|
||||
StepControl();
|
||||
if (!isSimulating) {
|
||||
CLAY_AUTO_ID(style::LeftPanelContainer(1, {
|
||||
.layout = { .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT() } }
|
||||
})) {
|
||||
elements::TextButton(CLAY_STRING("Randomize"), style::actionButton, &RandomizeFieldButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (simulation::drawDebugInfo) {
|
||||
DebugInfoLegend();
|
||||
}
|
||||
}
|
||||
|
||||
static void TryStep() {
|
||||
static uint64_t lastStep = 0;
|
||||
if (isSimulating) {
|
||||
double deltaTime{ (double)(SDL_GetTicks() - lastStep) * 0.001 };
|
||||
constexpr double targetDeltaTime = 1.0/24.0;
|
||||
if (!lockFramerate || deltaTime > targetDeltaTime) {
|
||||
lastStep = SDL_GetTicks();
|
||||
simulation::Step();
|
||||
}
|
||||
}
|
||||
static void SampleHeader() {
|
||||
elements::Header(CLAY_STRING("Left Panel"), 2, {
|
||||
.textColor = style::color::white
|
||||
});
|
||||
}
|
||||
|
||||
Clay_RenderCommandArray RenderApplication() {
|
||||
TryStep();
|
||||
Clay_BeginLayout();
|
||||
CLAY(CLAY_ID("OuterContainer"), style::Window()) {
|
||||
simulation::SetSimulationHovered(Clay_Hovered());
|
||||
PrimaryControls();
|
||||
CLAY_AUTO_ID(style::PanelContainer(0, {
|
||||
.layout = { .sizing = { CLAY_SIZING_PERCENT(0.15), CLAY_SIZING_GROW() } }
|
||||
})) {
|
||||
SampleHeader();
|
||||
}
|
||||
}
|
||||
return Clay_EndLayout();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
namespace elements {
|
||||
typedef void(*OnHoveredFn)(Clay_ElementId element, Clay_PointerData pointer, intptr_t data);
|
||||
|
||||
void TextButton(Clay_String text, Clay_Color color, OnHoveredFn onHovered, intptr_t onHoveredData = 0);
|
||||
void Toggle(Clay_String label, Clay_Color selected, bool &state);
|
||||
void Body(Clay_String string, Clay_TextElementConfig baseCfg = {});
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ void HandleEvent(SDL_Event const &event) {
|
|||
switch (event.type) {
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
if (shiftDown) {
|
||||
scrollMotion = (Clay_Vector2) { event.wheel.y * 4.f, -event.wheel.x * 4.f };
|
||||
scrollMotion = (Clay_Vector2) { event.wheel.y * 4.f, -event.wheel.x * -4.f };
|
||||
} else {
|
||||
scrollMotion = (Clay_Vector2) { -event.wheel.x * 4.f, event.wheel.y * 4.f };
|
||||
scrollMotion = (Clay_Vector2) { -event.wheel.x * -4.f, event.wheel.y * 4.f };
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
#include "application.h"
|
||||
#include "input.h"
|
||||
#include "resources.h"
|
||||
#include "simulation.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_error.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
|
@ -133,7 +132,6 @@ int main(int argc, char *argv[]) {
|
|||
Clay_UpdateScrollContainers(true, input::scrollMotion, deltaTime);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
simulation::Draw(renderer, 0.01);
|
||||
Clay_RenderCommandArray commands{ application::RenderApplication() };
|
||||
SDL_Clay_RenderClayCommands(&backendData, &commands);
|
||||
SDL_RenderPresent(renderer);
|
||||
|
|
|
|||
|
|
@ -1,327 +0,0 @@
|
|||
#include "simulation.h"
|
||||
#include "SDL3/SDL_timer.h"
|
||||
#include "input.h"
|
||||
#include "thread_pool.h"
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <SDL3/SDL_render.h>
|
||||
#include <algorithm>
|
||||
#include <clay/clay.h>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __glibc_likely
|
||||
#define likely(cond_) __glibc_likely(cond_)
|
||||
#else
|
||||
#define likely(cond_) (cond_)
|
||||
#endif
|
||||
|
||||
#define SIM_MULTITHREADING 1
|
||||
|
||||
namespace simulation {
|
||||
bool drawDebugInfo{ true };
|
||||
|
||||
struct ThreadWorkload {
|
||||
size_t seg_idx{0}, seg_len{1};
|
||||
std::mutex mtx{};
|
||||
std::vector<Cell> changes{};
|
||||
};
|
||||
|
||||
static std::set<Cell> living{};
|
||||
|
||||
static std::vector<std::shared_ptr<ThreadWorkload>> overpopulated{};
|
||||
static std::vector<std::shared_ptr<ThreadWorkload>> underpopulated{};
|
||||
static std::vector<std::shared_ptr<ThreadWorkload>> born{};
|
||||
static uint64_t generationStartTime{ 0 };
|
||||
static unsigned tasks{ 0 }; std::mutex tasksMutex{};
|
||||
static std::condition_variable tasksChanged{};
|
||||
|
||||
CellIterator::CellIterator(Cell begin, Cell end)
|
||||
: state{ begin } , begin{ begin }, end{ end } {}
|
||||
|
||||
CellIterator::CellIterator(Cell begin, Cell end, Cell state)
|
||||
: state{ state } , begin{ begin }, end{ end } {}
|
||||
|
||||
static inline bool CellIsAlive(Cell const &cell) {
|
||||
return living.contains(cell);
|
||||
}
|
||||
|
||||
CellIterator &CellIterator::operator++() {
|
||||
++(this->state.x);
|
||||
if (this->state.x == this->end.x) {
|
||||
this->state.x = this->begin.x;
|
||||
this->state.y = SDL_min(this->state.y + 1, this->end.y);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CellIterator::operator==(CellIterator const &src) const {
|
||||
return src.begin == this->begin && src.end == this->end && (src.state == this->state || src.at_end() == this->at_end());
|
||||
}
|
||||
|
||||
bool CellIterator::operator!=(CellIterator const &src) const {
|
||||
return src.begin != this->begin || src.end != this->end || (src.state != this->state && src.at_end() != this->at_end());
|
||||
}
|
||||
|
||||
Cell const &CellIterator::operator*() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
bool CellIterator::at_end() const {
|
||||
return this->state.y == this->end.y;
|
||||
}
|
||||
|
||||
static size_t CountNeighbors(Cell const &cell) {
|
||||
size_t count{ 0 };
|
||||
for (Cell const &c : CellRange{ {cell.x - 1, cell.y - 1}, { cell.x + 2, cell.y + 2} }) {
|
||||
if (c != cell && CellIsAlive(c)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void BlockUntilTasksDone() {
|
||||
std::unique_lock lock{ tasksMutex };
|
||||
while (tasks > 0) {
|
||||
tasksChanged.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void TaskBegin() {
|
||||
std::scoped_lock lock{ tasksMutex };
|
||||
tasks++;
|
||||
}
|
||||
|
||||
static void TaskComplete() {
|
||||
std::scoped_lock lock{ tasksMutex };
|
||||
if (--tasks == 0) {
|
||||
SDL_Log("Generation Complete %lfs", (double)(SDL_GetTicksNS() - generationStartTime) * 0.000000001);
|
||||
}
|
||||
tasksChanged.notify_all();
|
||||
}
|
||||
|
||||
static void FindOverpopulated(std::shared_ptr<ThreadWorkload> wl) {
|
||||
TaskBegin();
|
||||
std::scoped_lock lock{ wl->mtx };
|
||||
wl->changes.clear();
|
||||
|
||||
std::set<Cell>::iterator begin{ living.begin() };
|
||||
std::set<Cell>::iterator end{ living.begin() };
|
||||
|
||||
std::advance(begin, wl->seg_idx * wl->seg_len);
|
||||
std::advance(end, wl->seg_idx * wl->seg_len + wl->seg_len);
|
||||
|
||||
std::copy_if(begin, end, std::back_inserter(wl->changes),
|
||||
[&](Cell const &c) -> bool {
|
||||
return CountNeighbors(c) > 3;
|
||||
});
|
||||
TaskComplete();
|
||||
}
|
||||
|
||||
static void FindUnderpopulated(std::shared_ptr<ThreadWorkload> wl) {
|
||||
TaskBegin();
|
||||
std::scoped_lock lock{ wl->mtx };
|
||||
wl->changes.clear();
|
||||
|
||||
std::set<Cell>::iterator begin{ living.begin() };
|
||||
std::set<Cell>::iterator end{ living.begin() };
|
||||
|
||||
std::advance(begin, wl->seg_idx * wl->seg_len);
|
||||
std::advance(end, wl->seg_idx * wl->seg_len + wl->seg_len);
|
||||
|
||||
std::copy_if(begin, end, std::back_inserter(wl->changes),
|
||||
[&](Cell const &c) -> bool {
|
||||
return CountNeighbors(c) < 2;
|
||||
});
|
||||
TaskComplete();
|
||||
}
|
||||
|
||||
static void FindBorn(std::shared_ptr<ThreadWorkload> wl) {
|
||||
TaskBegin();
|
||||
std::scoped_lock lock{ wl->mtx };
|
||||
wl->changes.clear();
|
||||
|
||||
std::set<Cell>::iterator itr{ living.begin() };
|
||||
|
||||
std::advance(itr, wl->seg_idx * wl->seg_len);
|
||||
|
||||
std::for_each_n(itr, wl->seg_len, [&wl](Cell const ¢er){
|
||||
CellRange range{{ center.x - 1, center.y - 1 }, {center.x + 2, center.y + 2 }};
|
||||
std::copy_if(range.begin(), range.end(), std::back_inserter(wl->changes),
|
||||
[&](Cell const &c) -> bool {
|
||||
return !CellIsAlive(c) && CountNeighbors(c) == 3;
|
||||
});
|
||||
});
|
||||
TaskComplete();
|
||||
}
|
||||
|
||||
static void PopulateChanges() {
|
||||
static bool first_run{ true };
|
||||
#if SIM_MULTITHREADING
|
||||
BlockUntilTasksDone();
|
||||
constexpr size_t split{ 4 };
|
||||
if (first_run) {
|
||||
first_run = false;
|
||||
born.resize(split);
|
||||
underpopulated.resize(split);
|
||||
overpopulated.resize(split);
|
||||
}
|
||||
|
||||
SDL_Log("Multithreading ON");
|
||||
generationStartTime = SDL_GetTicksNS();
|
||||
size_t const seg_length{ living.size() / split };
|
||||
for (size_t i{ 0 }; i < split; ++i) {
|
||||
if (overpopulated[i] == nullptr)
|
||||
overpopulated[i] = std::make_shared<ThreadWorkload>();
|
||||
{ std::scoped_lock lock{ overpopulated[i]->mtx };
|
||||
overpopulated[i]->seg_idx = i; overpopulated[i]->seg_len = seg_length;
|
||||
threading::tasks.ScheduleTask(std::bind(FindOverpopulated, overpopulated[i]));
|
||||
}
|
||||
if (underpopulated[i] == nullptr)
|
||||
underpopulated[i] = std::make_shared<ThreadWorkload>();
|
||||
{ std::scoped_lock lock{ underpopulated[i]->mtx };
|
||||
underpopulated[i]->seg_idx = i; underpopulated[i]->seg_len = seg_length;
|
||||
threading::tasks.ScheduleTask(std::bind(FindUnderpopulated, underpopulated[i]));
|
||||
}
|
||||
if (born[i] == nullptr)
|
||||
born[i] = std::make_shared<ThreadWorkload>();
|
||||
{ std::scoped_lock lock{ born[i]->mtx };
|
||||
born[i]->seg_idx = i; born[i]->seg_len = seg_length;
|
||||
threading::tasks.ScheduleTask(std::bind(FindBorn, born[i]));
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (first_run) {
|
||||
first_run = false;
|
||||
overpopulated.emplace_back(std::make_shared<ThreadWorkload>());
|
||||
overpopulated[0]->seg_idx = 0;
|
||||
overpopulated[0]->seg_len = living.size();
|
||||
underpopulated.emplace_back(std::make_shared<ThreadWorkload>());
|
||||
underpopulated[0]->seg_idx = 0;
|
||||
underpopulated[0]->seg_len = living.size();
|
||||
born.emplace_back(std::make_shared<ThreadWorkload>());
|
||||
born[0]->seg_idx = 0;
|
||||
born[0]->seg_len = living.size();
|
||||
}
|
||||
SDL_Log("Multithreading OFF");
|
||||
{ std::scoped_lock lock{ tasksMutex };
|
||||
tasks = 3; }
|
||||
generationStartTime = SDL_GetTicksNS();
|
||||
FindOverpopulated(overpopulated[0]);
|
||||
FindUnderpopulated(underpopulated[0]);
|
||||
FindBorn(born[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InitializeRandom(size_t livingChance, int64_t fillArea) {
|
||||
BlockUntilTasksDone();
|
||||
living.clear();
|
||||
|
||||
Cell itr{ 0, 0 };
|
||||
while (itr.y < fillArea) {
|
||||
if (std::rand() % livingChance == 0 ) {
|
||||
living.insert(itr);
|
||||
}
|
||||
++itr.x;
|
||||
if (itr.x == fillArea) {
|
||||
itr.x = 0;
|
||||
++itr.y;
|
||||
}
|
||||
}
|
||||
PopulateChanges();
|
||||
}
|
||||
|
||||
void Step() {
|
||||
BlockUntilTasksDone();
|
||||
for (std::shared_ptr<ThreadWorkload> wl : underpopulated) {
|
||||
if (wl == nullptr) continue;
|
||||
std::scoped_lock l{ wl->mtx };
|
||||
for (Cell const &cell : wl->changes)
|
||||
living.erase(cell);
|
||||
}
|
||||
for (std::shared_ptr<ThreadWorkload> wl : overpopulated) {
|
||||
if (wl == nullptr) continue;
|
||||
std::scoped_lock l{ wl->mtx };
|
||||
for (Cell const &cell : wl->changes)
|
||||
living.erase(cell);
|
||||
}
|
||||
for (std::shared_ptr<ThreadWorkload> wl : born) {
|
||||
if (wl == nullptr) continue;
|
||||
std::scoped_lock l{ wl->mtx };
|
||||
for (Cell const &cell : wl->changes)
|
||||
living.insert(cell);
|
||||
}
|
||||
|
||||
PopulateChanges();
|
||||
}
|
||||
|
||||
static bool simulationHovered{ false };
|
||||
static SDL_FPoint viewOffset{ 0, 0 };
|
||||
|
||||
void Draw(SDL_Renderer *renderer, double cellSizePercent) {
|
||||
if (simulationHovered) {
|
||||
viewOffset.x += input::scrollMotion.x;
|
||||
viewOffset.y += input::scrollMotion.y;
|
||||
}
|
||||
int w;
|
||||
SDL_GetCurrentRenderOutputSize(renderer, &w, nullptr);
|
||||
float const cellWidth = static_cast<float>(w) * cellSizePercent;
|
||||
SDL_FRect cellRect{
|
||||
0, 0, cellWidth, cellWidth
|
||||
};
|
||||
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
||||
for (Cell const &cell : living) {
|
||||
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
||||
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
||||
SDL_RenderFillRect(renderer, &cellRect);
|
||||
}
|
||||
if (!drawDebugInfo) {
|
||||
return;
|
||||
}
|
||||
BlockUntilTasksDone();
|
||||
for (std::shared_ptr<ThreadWorkload> wl : overpopulated) {
|
||||
std::scoped_lock l{ wl->mtx };
|
||||
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
|
||||
for (Cell const &cell : wl->changes) {
|
||||
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
||||
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
||||
SDL_RenderRect(renderer, &cellRect);
|
||||
}
|
||||
}
|
||||
for (std::shared_ptr<ThreadWorkload> wl : underpopulated) {
|
||||
std::scoped_lock l{ wl->mtx };
|
||||
SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
|
||||
for (Cell const &cell : wl->changes) {
|
||||
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
||||
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
||||
SDL_RenderRect(renderer, &cellRect);
|
||||
}
|
||||
}
|
||||
for (std::shared_ptr<ThreadWorkload> wl : born) {
|
||||
std::scoped_lock l{ wl->mtx };
|
||||
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
|
||||
for (Cell const &cell : wl->changes) {
|
||||
cellRect.x = (viewOffset.x + cell.x) * cellRect.w;
|
||||
cellRect.y = (viewOffset.y + cell.y) * cellRect.h;
|
||||
SDL_RenderRect(renderer, &cellRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetSimulationHovered(bool value) {
|
||||
simulationHovered = value;
|
||||
}
|
||||
bool operator==(Cell const &lhs, Cell const &rhs) {
|
||||
return lhs.x == rhs.x && lhs.y == rhs.y;
|
||||
}
|
||||
bool operator!=(Cell const &lhs, Cell const &rhs) {
|
||||
return lhs.x != rhs.x || lhs.y != rhs.y;
|
||||
}
|
||||
|
||||
bool operator<(Cell const &lhs, Cell const &rhs) {
|
||||
if (lhs.y == rhs.y) return lhs.x < rhs.x;
|
||||
else return lhs.y < rhs.y;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
#ifndef SIMULATION_H
|
||||
#define SIMULATION_H
|
||||
|
||||
#include <SDL3/SDL_rect.h>
|
||||
#include <SDL3/SDL_render.h>
|
||||
#include <clay/clay.h>
|
||||
|
||||
namespace simulation {
|
||||
struct Cell {
|
||||
int64_t x{ 0 }, y{ 0 };
|
||||
|
||||
};
|
||||
|
||||
class CellIterator {
|
||||
friend class CellRange;
|
||||
public: CellIterator(Cell begin, Cell end);
|
||||
CellIterator &operator++();
|
||||
CellIterator &operator=(CellIterator const &src);
|
||||
bool operator==(CellIterator const &src) const;
|
||||
bool operator!=(CellIterator const &src) const;
|
||||
Cell const &operator*() const;
|
||||
bool at_end() const;
|
||||
private: CellIterator(Cell begin, Cell end, Cell state);
|
||||
Cell state{ 0, 0 };
|
||||
Cell begin{ 0, 0 }, end{ 0, 0 };
|
||||
};
|
||||
|
||||
class CellRange {
|
||||
private:
|
||||
Cell beginCell{ 0, 0 }, endCell{ 0, 0 };
|
||||
public: CellRange(Cell begin, Cell end) : beginCell{ begin }, endCell{ end } {}
|
||||
CellIterator begin() { return CellIterator{ beginCell, endCell }; }
|
||||
CellIterator end() { return CellIterator{ beginCell, endCell, endCell }; }
|
||||
};
|
||||
|
||||
extern bool drawDebugInfo;
|
||||
|
||||
void InitializeRandom(size_t livingChance, int64_t fillArea);
|
||||
void Step();
|
||||
void Draw(SDL_Renderer *renderer, double cellSizePercent);
|
||||
void SetSimulationHovered(bool value);
|
||||
|
||||
bool operator==(Cell const &lhs, Cell const &rhs);
|
||||
bool operator!=(Cell const &lhs, Cell const &rhs);
|
||||
bool operator<(Cell const &lhs, Cell const &rhs);
|
||||
}
|
||||
|
||||
#endif // !SIMULATION_H
|
||||
|
|
@ -19,7 +19,7 @@ Clay_ElementDeclaration PanelContainer(size_t depth, Clay_ElementDeclaration bas
|
|||
CLAY_BORDER_OUTSIDE(2)
|
||||
};
|
||||
baseCfg.cornerRadius = defaultRadiusAll;
|
||||
baseCfg.layout.padding = CLAY_PADDING_ALL(16);
|
||||
baseCfg.layout.padding = CLAY_PADDING_ALL(8);
|
||||
return baseCfg;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ void ThreadPool::ScheduleTask(TaskFunc fn) {
|
|||
void ThreadPool::ThreadFn() {
|
||||
TaskFunc function;
|
||||
for(;;) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{ this->lock };
|
||||
{ std::unique_lock<std::mutex> lock{ this->lock };
|
||||
while (!this->shutdown && this->taskQueue.empty()) {
|
||||
this->threadNotifier.wait(lock);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue