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)
 | 
			
		||||
project(CHANGEME)
 | 
			
		||||
project(GameOfLife)
 | 
			
		||||
 | 
			
		||||
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(CHANGEME ${source_files})
 | 
			
		||||
target_link_libraries(CHANGEME PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3)
 | 
			
		||||
add_executable(GameOfLife ${source_files})
 | 
			
		||||
target_link_libraries(GameOfLife 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(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:
 | 
			
		||||
	# BUILDING
 | 
			
		||||
	cmake --build build
 | 
			
		||||
	bear -- cmake --build build
 | 
			
		||||
 | 
			
		||||
run:
 | 
			
		||||
	cd bin/ && CHANGEME
 | 
			
		||||
	cd bin/ && DiceGui
 | 
			
		||||
 | 
			
		||||
configure:
 | 
			
		||||
	# CONFIGURING WITH PREMAKE
 | 
			
		||||
	cmake -S. -Bbuild -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
 | 
			
		||||
	cmake -S. -Bbuild
 | 
			
		||||
 | 
			
		||||
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,24 +1,172 @@
 | 
			
		|||
#include "application.h"
 | 
			
		||||
#include "style.h"
 | 
			
		||||
#include "elements.h"
 | 
			
		||||
#include "simulation.h"
 | 
			
		||||
#include <SDL3/SDL.h>
 | 
			
		||||
#include <clay/clay.h>
 | 
			
		||||
 | 
			
		||||
namespace application {
 | 
			
		||||
static void SampleHeader() {
 | 
			
		||||
	elements::Header(CLAY_STRING("Left Panel"), 2, {
 | 
			
		||||
		.textColor = style::color::white
 | 
			
		||||
	});
 | 
			
		||||
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();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Clay_RenderCommandArray RenderApplication() {
 | 
			
		||||
	TryStep();
 | 
			
		||||
	Clay_BeginLayout();
 | 
			
		||||
	CLAY(CLAY_ID("OuterContainer"), style::Window()) {
 | 
			
		||||
		CLAY_AUTO_ID(style::PanelContainer(0, {
 | 
			
		||||
			.layout = { .sizing = { CLAY_SIZING_PERCENT(0.15), CLAY_SIZING_GROW() } }
 | 
			
		||||
		})) {
 | 
			
		||||
			SampleHeader();
 | 
			
		||||
		}
 | 
			
		||||
		simulation::SetSimulationHovered(Clay_Hovered());
 | 
			
		||||
		PrimaryControls();
 | 
			
		||||
	}
 | 
			
		||||
	return Clay_EndLayout();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
 | 
			
		||||
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,6 +3,7 @@
 | 
			
		|||
#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>
 | 
			
		||||
| 
						 | 
				
			
			@ -132,6 +133,7 @@ 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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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)
 | 
			
		||||
	};
 | 
			
		||||
	baseCfg.cornerRadius = defaultRadiusAll;
 | 
			
		||||
	baseCfg.layout.padding = CLAY_PADDING_ALL(8);
 | 
			
		||||
	baseCfg.layout.padding = CLAY_PADDING_ALL(16);
 | 
			
		||||
	return baseCfg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,8 @@ 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