Compare commits
No commits in common. "a3396bb6af88ebceaebed565d902b7af52ad228c" and "8538c912062481c80d1e52f0f19f1ffe298c66ec" have entirely different histories.
a3396bb6af
...
8538c91206
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 3.21)
|
||||||
project(CHANGEME)
|
project(GameOfLife)
|
||||||
|
|
||||||
set(CMAKE_BINARY_DIR "${CMAKE_SOURCE_DIR}/bin")
|
set(CMAKE_BINARY_DIR "${CMAKE_SOURCE_DIR}/bin")
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
|
|
@ -15,11 +15,11 @@ add_subdirectory(vendor/SDL3/ EXCLUDE_FROM_ALL)
|
||||||
set(SDLTTF_VENDORED ON)
|
set(SDLTTF_VENDORED ON)
|
||||||
add_subdirectory(vendor/SDL3_ttf/ EXCLUDE_FROM_ALL)
|
add_subdirectory(vendor/SDL3_ttf/ EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
add_executable(CHANGEME ${source_files})
|
add_executable(GameOfLife ${source_files})
|
||||||
target_link_libraries(CHANGEME PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3)
|
target_link_libraries(GameOfLife PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3)
|
||||||
|
|
||||||
add_custom_target(copy_assets
|
add_custom_target(copy_assets
|
||||||
COMMAND ${CMAKE_COMMAND} -E
|
COMMAND ${CMAKE_COMMAND} -E
|
||||||
copy_directory ${CMAKE_SOURCE_DIR}/assets/ ${CMAKE_BINARY_DIR}/assets
|
copy_directory ${CMAKE_SOURCE_DIR}/assets/ ${CMAKE_BINARY_DIR}/assets
|
||||||
)
|
)
|
||||||
add_dependencies(CHANGEME copy_assets)
|
add_dependencies(GameOfLife copy_assets)
|
||||||
|
|
|
||||||
119
README.md
119
README.md
|
|
@ -1,119 +0,0 @@
|
||||||
# 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,19 +1,14 @@
|
||||||
build:
|
build:
|
||||||
# BUILDING
|
# BUILDING
|
||||||
cmake --build build
|
bear -- cmake --build build
|
||||||
|
|
||||||
run:
|
run:
|
||||||
cd bin/ && CHANGEME
|
cd bin/ && DiceGui
|
||||||
|
|
||||||
configure:
|
configure:
|
||||||
# CONFIGURING WITH PREMAKE
|
# CONFIGURING WITH PREMAKE
|
||||||
cmake -S. -Bbuild -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
cmake -S. -Bbuild
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
# CLEANING BUILD ARTEFACTS
|
|
||||||
rm -r bin/**
|
rm -r bin/**
|
||||||
rm -r build/**
|
rm -r build/**
|
||||||
|
|
||||||
set-project-name projectname: clean
|
|
||||||
git remote set-url origin ""
|
|
||||||
sed -i "s/CHANGEME/{{projectname}}/g" ./CMakeLists.txt ./justfile
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,172 @@
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "style.h"
|
#include "style.h"
|
||||||
#include "elements.h"
|
#include "elements.h"
|
||||||
|
#include "simulation.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <clay/clay.h>
|
#include <clay/clay.h>
|
||||||
|
|
||||||
namespace application {
|
namespace application {
|
||||||
static void SampleHeader() {
|
static bool isSimulating{ false };
|
||||||
elements::Header(CLAY_STRING("Left Panel"), 2, {
|
static bool lockFramerate{ true };
|
||||||
.textColor = style::color::white
|
|
||||||
});
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Clay_RenderCommandArray RenderApplication() {
|
Clay_RenderCommandArray RenderApplication() {
|
||||||
|
TryStep();
|
||||||
Clay_BeginLayout();
|
Clay_BeginLayout();
|
||||||
CLAY(CLAY_ID("OuterContainer"), style::Window()) {
|
CLAY(CLAY_ID("OuterContainer"), style::Window()) {
|
||||||
CLAY_AUTO_ID(style::PanelContainer(0, {
|
simulation::SetSimulationHovered(Clay_Hovered());
|
||||||
.layout = { .sizing = { CLAY_SIZING_PERCENT(0.15), CLAY_SIZING_GROW() } }
|
PrimaryControls();
|
||||||
})) {
|
|
||||||
SampleHeader();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Clay_EndLayout();
|
return Clay_EndLayout();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
namespace elements {
|
namespace elements {
|
||||||
typedef void(*OnHoveredFn)(Clay_ElementId element, Clay_PointerData pointer, intptr_t data);
|
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 TextButton(Clay_String text, Clay_Color color, OnHoveredFn onHovered, intptr_t onHoveredData = 0);
|
||||||
void Toggle(Clay_String label, Clay_Color selected, bool &state);
|
void Toggle(Clay_String label, Clay_Color selected, bool &state);
|
||||||
void Body(Clay_String string, Clay_TextElementConfig baseCfg = {});
|
void Body(Clay_String string, Clay_TextElementConfig baseCfg = {});
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ void HandleEvent(SDL_Event const &event) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_EVENT_MOUSE_WHEEL:
|
case SDL_EVENT_MOUSE_WHEEL:
|
||||||
if (shiftDown) {
|
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 {
|
} 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;
|
break;
|
||||||
case SDL_EVENT_MOUSE_MOTION:
|
case SDL_EVENT_MOUSE_MOTION:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
|
#include "simulation.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3/SDL_error.h>
|
#include <SDL3/SDL_error.h>
|
||||||
#include <SDL3/SDL_events.h>
|
#include <SDL3/SDL_events.h>
|
||||||
|
|
@ -132,6 +133,7 @@ int main(int argc, char *argv[]) {
|
||||||
Clay_UpdateScrollContainers(true, input::scrollMotion, deltaTime);
|
Clay_UpdateScrollContainers(true, input::scrollMotion, deltaTime);
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
|
simulation::Draw(renderer, 0.01);
|
||||||
Clay_RenderCommandArray commands{ application::RenderApplication() };
|
Clay_RenderCommandArray commands{ application::RenderApplication() };
|
||||||
SDL_Clay_RenderClayCommands(&backendData, &commands);
|
SDL_Clay_RenderClayCommands(&backendData, &commands);
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
|
|
|
||||||
327
src/simulation.cpp
Normal file
327
src/simulation.cpp
Normal file
|
|
@ -0,0 +1,327 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/simulation.h
Normal file
48
src/simulation.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#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)
|
CLAY_BORDER_OUTSIDE(2)
|
||||||
};
|
};
|
||||||
baseCfg.cornerRadius = defaultRadiusAll;
|
baseCfg.cornerRadius = defaultRadiusAll;
|
||||||
baseCfg.layout.padding = CLAY_PADDING_ALL(8);
|
baseCfg.layout.padding = CLAY_PADDING_ALL(16);
|
||||||
return baseCfg;
|
return baseCfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ void ThreadPool::ScheduleTask(TaskFunc fn) {
|
||||||
void ThreadPool::ThreadFn() {
|
void ThreadPool::ThreadFn() {
|
||||||
TaskFunc function;
|
TaskFunc function;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
{ std::unique_lock<std::mutex> lock{ this->lock };
|
{
|
||||||
|
std::unique_lock<std::mutex> lock{ this->lock };
|
||||||
while (!this->shutdown && this->taskQueue.empty()) {
|
while (!this->shutdown && this->taskQueue.empty()) {
|
||||||
this->threadNotifier.wait(lock);
|
this->threadNotifier.wait(lock);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue