feat: converted project to template
This commit is contained in:
		
							parent
							
								
									0f80dc8ca6
								
							
						
					
					
						commit
						a3396bb6af
					
				| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
cmake_minimum_required(VERSION 3.21)
 | 
					cmake_minimum_required(VERSION 3.21)
 | 
				
			||||||
project(GameOfLife)
 | 
					project(CHANGEME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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(GameOfLife ${source_files})
 | 
					add_executable(CHANGEME ${source_files})
 | 
				
			||||||
target_link_libraries(GameOfLife PRIVATE SDL3_ttf::SDL3_ttf SDL3::SDL3)
 | 
					target_link_libraries(CHANGEME 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(GameOfLife copy_assets)
 | 
					add_dependencies(CHANGEME copy_assets)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							| 
						 | 
					@ -1,16 +1,26 @@
 | 
				
			||||||
# Conway's Game of Life SDL3 implementation
 | 
					# 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
 | 
					## Compiling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> Remember to `git submodule update --init --recursive`!
 | 
					> Remember to `git submodule update --init --recursive`!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Just use [`just`](https://just.systems/man/en/)
 | 
					### Just again
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Use the justfile if you have `just` and `bear` installed:
 | 
					`just configure` runs cmake configuration and generates a `compile_commands.json`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`just configure` runs cmake configuration.
 | 
					`just build` runs the cmake build.
 | 
				
			||||||
 | 
					 | 
				
			||||||
`just build` runs the cmake build and generates a `compile_commands.json` (assuming you have `bear` installed).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### CMake works too, I guess
 | 
					### CMake works too, I guess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,10 +32,6 @@ Same as always
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Files
 | 
					## Files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### simulation.h/cpp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The Conway's Game of Life generation ticking and rendering.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### thread_pool.h/cpp
 | 
					### thread_pool.h/cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Thread pool used for parallel GoL generation ticking.
 | 
					Thread pool used for parallel GoL generation ticking.
 | 
				
			||||||
| 
						 | 
					@ -54,13 +60,12 @@ Handling of input events.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Entrypoint, setup, and main application loop.
 | 
					Entrypoint, setup, and main application loop.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Prerequisites
 | 
					## Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Compiler capable of C++23 and C23.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* CMake 3.21 or higher
 | 
					* CMake 3.21 or higher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Compiler capable of C++23 and C23.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Dependencies
 | 
					## Dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### SDL3
 | 
					### SDL3
 | 
				
			||||||
| 
						 | 
					@ -79,9 +84,11 @@ Included as files in `vendor/clay/clay.h`; Single header library.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Included as files in `vendor/renderer/` and compiled as part of the project.
 | 
					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
 | 
					## Code Standards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Keep program structure as simple as possible. No `Application` class or other Java-isms. Prefer namespaces with static lifetime variables.
 | 
					* 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.
 | 
					* Use STL where possible. Don't reinvent the wheel.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,12 +100,16 @@ Included as files in `vendor/renderer/` and compiled as part of the project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* const applies to the name to it's left, so it goes after the type, not `const int x;` but `int const x`.
 | 
					* 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. `void Function(Object const &inRef)`
 | 
					* \* and & flush with the declaration name. `Type const &Function(Type &inRef)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> (1) Bracket exceptions:
 | 
					> (1) Bracket exceptions:
 | 
				
			||||||
> * using scoped_lock in arbitrary blocks, prefer tailed lisp brackets
 | 
					> * using scoped_lock in arbitrary blocks, prefer tailed lisp brackets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
void MyFunction() { // K&R here
 | 
					struct Data {
 | 
				
			||||||
 | 
					    int x{ 0 }, y{ 0 };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					void MyFunction(Data const &data) { // K&R here
 | 
				
			||||||
    DoAsynchronousThings();
 | 
					    DoAsynchronousThings();
 | 
				
			||||||
    { scoped_lock lock{ myMutex };
 | 
					    { scoped_lock lock{ myMutex };
 | 
				
			||||||
        myVariable++;
 | 
					        myVariable++;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								justfile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								justfile
									
									
									
									
									
								
							| 
						 | 
					@ -1,14 +1,19 @@
 | 
				
			||||||
build:
 | 
					build:
 | 
				
			||||||
	# BUILDING
 | 
						# BUILDING
 | 
				
			||||||
	bear -- cmake --build build
 | 
						cmake --build build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run:
 | 
					run:
 | 
				
			||||||
	cd bin/ && DiceGui
 | 
						cd bin/ && CHANGEME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
configure:
 | 
					configure:
 | 
				
			||||||
	# CONFIGURING WITH PREMAKE
 | 
						# CONFIGURING WITH PREMAKE
 | 
				
			||||||
	cmake -S. -Bbuild
 | 
						cmake -S. -Bbuild -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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,173 +1,24 @@
 | 
				
			||||||
#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 bool isSimulating{ false };
 | 
					static void SampleHeader() {
 | 
				
			||||||
static bool lockFramerate{ true };
 | 
						elements::Header(CLAY_STRING("Left Panel"), 2, {
 | 
				
			||||||
 | 
							.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, 300);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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("Threaded"), style::actionButton, simulation::threadedDesired);
 | 
					 | 
				
			||||||
		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()) {
 | 
				
			||||||
		simulation::SetSimulationHovered(Clay_Hovered());
 | 
							CLAY_AUTO_ID(style::PanelContainer(0, {
 | 
				
			||||||
		PrimaryControls();
 | 
								.layout = { .sizing = { CLAY_SIZING_PERCENT(0.15), CLAY_SIZING_GROW() } }
 | 
				
			||||||
 | 
							})) {
 | 
				
			||||||
 | 
								SampleHeader();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return Clay_EndLayout();
 | 
						return Clay_EndLayout();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 = {});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@
 | 
				
			||||||
#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>
 | 
				
			||||||
| 
						 | 
					@ -133,7 +132,6 @@ 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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,356 +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 {
 | 
					 | 
				
			||||||
// Encapsulates the data relevant to a GoL rule's workload
 | 
					 | 
				
			||||||
struct ThreadWorkload {
 | 
					 | 
				
			||||||
	size_t seg_idx{0}, seg_len{1};
 | 
					 | 
				
			||||||
	std::mutex mtx{};
 | 
					 | 
				
			||||||
	std::vector<Cell> changes{};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool drawDebugInfo{ true }; // draw next step's changes over current state
 | 
					 | 
				
			||||||
bool threadedDesired{ true }; // multithread generation steps
 | 
					 | 
				
			||||||
static bool actuallyThreaded{ false }; // notes whether or not currently ongoing tasks are multithreaded
 | 
					 | 
				
			||||||
size_t threadsPerTask{ std::thread::hardware_concurrency() };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static std::set<Cell> living{}; // The set of currently alive cells
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Ruleset workload abstraction
 | 
					 | 
				
			||||||
static std::vector<std::shared_ptr<ThreadWorkload>> overpopulated{};
 | 
					 | 
				
			||||||
static std::vector<std::shared_ptr<ThreadWorkload>> underpopulated{};
 | 
					 | 
				
			||||||
static std::vector<std::shared_ptr<ThreadWorkload>> born{};
 | 
					 | 
				
			||||||
// Multithreading
 | 
					 | 
				
			||||||
static unsigned tasks{ 0 };
 | 
					 | 
				
			||||||
static std::mutex tasksMutex{};
 | 
					 | 
				
			||||||
static std::condition_variable tasksChanged{};
 | 
					 | 
				
			||||||
// ns timestamp of the start of the last generation change calculation
 | 
					 | 
				
			||||||
static uint64_t generationStartTime{ 0 };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Block the thread until all tasks are done
 | 
					 | 
				
			||||||
// after this function, all code in this file is synchronous, until another thread is started
 | 
					 | 
				
			||||||
static void BlockUntilTasksDone() {
 | 
					 | 
				
			||||||
	if (actuallyThreaded) {
 | 
					 | 
				
			||||||
		std::unique_lock lock{ tasksMutex };
 | 
					 | 
				
			||||||
		while (tasks > 0) {
 | 
					 | 
				
			||||||
			tasksChanged.wait(lock);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else while (tasks > 0) { }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void TaskBegin() {
 | 
					 | 
				
			||||||
	std::scoped_lock lock{ tasksMutex };
 | 
					 | 
				
			||||||
	tasks++;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void TaskComplete() {
 | 
					 | 
				
			||||||
	if (actuallyThreaded) {
 | 
					 | 
				
			||||||
		std::scoped_lock lock{ tasksMutex };
 | 
					 | 
				
			||||||
		if (--tasks == 0) {
 | 
					 | 
				
			||||||
			SDL_Log("Generation Complete %lfs", (double)(SDL_GetTicksNS() - generationStartTime) * 0.000000001);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		tasksChanged.notify_all();
 | 
					 | 
				
			||||||
	} else if (tasks == 3) {
 | 
					 | 
				
			||||||
		SDL_Log("Generation complete %lfs", (double)(SDL_GetTicksNS() - generationStartTime) * 0.000000001);
 | 
					 | 
				
			||||||
		tasks = 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 inline void TickGenerationThreaded() {
 | 
					 | 
				
			||||||
	SDL_Log("Multithreading ON");
 | 
					 | 
				
			||||||
	generationStartTime = SDL_GetTicksNS();
 | 
					 | 
				
			||||||
	size_t const seg_length{ living.size() / threadsPerTask };
 | 
					 | 
				
			||||||
	for (size_t i{ 0 }; i < threadsPerTask; ++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]));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static inline void TickGenerationSynchronous() {
 | 
					 | 
				
			||||||
	SDL_Log("Multithreading OFF");
 | 
					 | 
				
			||||||
	generationStartTime = SDL_GetTicksNS();
 | 
					 | 
				
			||||||
	born[0]->seg_idx = underpopulated[0]->seg_idx = overpopulated[0]->seg_idx = 0;
 | 
					 | 
				
			||||||
	born[0]->seg_len = underpopulated[0]->seg_len = overpopulated[0]->seg_len = living.size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	FindOverpopulated(overpopulated[0]);
 | 
					 | 
				
			||||||
	FindUnderpopulated(underpopulated[0]);
 | 
					 | 
				
			||||||
	FindBorn(born[0]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void PopulateChanges() {
 | 
					 | 
				
			||||||
	static bool first_run{ true };
 | 
					 | 
				
			||||||
	BlockUntilTasksDone();
 | 
					 | 
				
			||||||
	// for some reason three tasks per thread is faster than one task per thread, i don't get it either
 | 
					 | 
				
			||||||
	if (threadedDesired) {
 | 
					 | 
				
			||||||
		if (first_run || !actuallyThreaded) {
 | 
					 | 
				
			||||||
			actuallyThreaded = true;
 | 
					 | 
				
			||||||
			first_run = false;
 | 
					 | 
				
			||||||
			born.resize(threadsPerTask);
 | 
					 | 
				
			||||||
			underpopulated.resize(threadsPerTask);
 | 
					 | 
				
			||||||
			overpopulated.resize(threadsPerTask);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		TickGenerationThreaded();
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		if (first_run || actuallyThreaded) {
 | 
					 | 
				
			||||||
			actuallyThreaded = false;
 | 
					 | 
				
			||||||
			first_run = false;
 | 
					 | 
				
			||||||
			born.resize(1);
 | 
					 | 
				
			||||||
			underpopulated.resize(1);
 | 
					 | 
				
			||||||
			overpopulated.resize(1);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		TickGenerationSynchronous();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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, h;
 | 
					 | 
				
			||||||
	SDL_GetCurrentRenderOutputSize(renderer, &w, &h);
 | 
					 | 
				
			||||||
	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;
 | 
					 | 
				
			||||||
		if (cellRect.x < -cellRect.w || cellRect.y < -cellRect.h || cellRect.x > w || cellRect.y > h)
 | 
					 | 
				
			||||||
			continue;
 | 
					 | 
				
			||||||
		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;
 | 
					 | 
				
			||||||
			if (cellRect.x < -cellRect.w || cellRect.y < -cellRect.h || cellRect.x > w || cellRect.y > h)
 | 
					 | 
				
			||||||
				continue;
 | 
					 | 
				
			||||||
			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;
 | 
					 | 
				
			||||||
			if (cellRect.x < -cellRect.w || cellRect.y < -cellRect.h || cellRect.x > w || cellRect.y > h)
 | 
					 | 
				
			||||||
				continue;
 | 
					 | 
				
			||||||
			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;
 | 
					 | 
				
			||||||
			if (cellRect.x < -cellRect.w || cellRect.y < -cellRect.h || cellRect.x > w || cellRect.y > h)
 | 
					 | 
				
			||||||
				continue;
 | 
					 | 
				
			||||||
			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,50 +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;
 | 
					 | 
				
			||||||
extern bool threadedDesired;
 | 
					 | 
				
			||||||
extern size_t threadsPerTask;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
		Loading…
	
		Reference in a new issue