From 8384f0f4b4309272dfd4e8dfa69683a4eaf4e43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?koukaku01=20=E8=A1=8C=E5=AE=A2?= Date: Sat, 13 Sep 2025 17:42:45 +0100 Subject: [PATCH 01/12] Migrate build system from Visual Studio to CMake - Remove Visual Studio solution and project files - Add comprehensive CMake build system with cross-platform support - Update .gitignore for CMake and IDE artifacts - Fix include path casing for GUIManager - Update README with new build instructions and project structure - Add CMake package config template - Improve main.cpp with enhanced error handling and debug output --- .gitignore | 45 +++-- CMakeLists.txt | 139 +++++++++++++++ EcoSimEngine.sln | 31 ---- EcoSimEngine.vcxproj | 203 ---------------------- EcoSimEngine.vcxproj.filters | 184 -------------------- README.md | 99 +++++++---- cmake/EcoSimEngineConfig.cmake.in | 5 + include/EcoSimEngine/SimulationEngine.hpp | 2 +- src/GUI/GUIManager.cpp | 6 +- src/main.cpp | 32 ++-- 10 files changed, 268 insertions(+), 478 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 EcoSimEngine.sln delete mode 100644 EcoSimEngine.vcxproj delete mode 100644 EcoSimEngine.vcxproj.filters create mode 100644 cmake/EcoSimEngineConfig.cmake.in diff --git a/.gitignore b/.gitignore index 0dd8404..d0332e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,38 @@ -# Visual Studio temp & settings +#------------------------- +# Visual Studio +#------------------------- .vs/ -*.vcxproj.user *.suo +*.user +*.vcxproj +*.vcxproj.filters -# Build outputs -Debug/ -Release/ -*.exe -*.dll -*.lib -*.obj -*.pdb +#------------------------- +# CMake build directories +#------------------------- +/build/ +/out/ +/out/build/ +/cmake-build-*/ -# OS stuff +CMakeFiles/ +CMakeCache.txt +CMakeScripts/ +Testing/ +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json + +#------------------------- +# IDE / Editor +#------------------------- +*.code-workspace +*.idea/ +*.vscode/ + +#------------------------- +# OS files +#------------------------- Thumbs.db -.DS_Store +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d76cdd7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,139 @@ +cmake_minimum_required(VERSION 3.28) +project(EcoSimEngine VERSION 0.1.0 LANGUAGES CXX) + +# --------------------- +# C++ standard settings +# --------------------- +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) # Avoid compiler-specific extensions + +# --------------------- +# Output folders +# --------------------- +# All executables will be placed in build/bin, with subfolders per configuration +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/Debug) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/Release) + +# --------------------- +# Source files +# --------------------- +# Glob all project and ImGui/ImGui-SFML source files +file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS src/*.cpp) +file(GLOB IMGUI_SOURCES CONFIGURE_DEPENDS + external/imgui/*.cpp + external/imgui/backends/*.cpp + external/imgui-sfml/*.cpp +) + +# Define executable +add_executable(EcoSimEngine ${PROJECT_SOURCES} ${IMGUI_SOURCES}) + +# --------------------- +# Include directories +# --------------------- +# Use PRIVATE to prevent leaking internal includes to dependents +target_include_directories(EcoSimEngine PRIVATE + include + external + external/imgui + external/imgui-sfml + external/nlohmann +) + +# --------------------- +# SFML 3 setup +# --------------------- +include(FetchContent) + +# Try to find SFML installed on the system first +find_package(SFML 3 COMPONENTS Graphics Window System Audio QUIET) + +# On Windows, allow manual override of SFML_DIR +if(NOT SFML_FOUND AND WIN32) + set(SFML_DIR "D:/SFML-3.0.0/lib/cmake/SFML") # <-- Update path to local SFML if needed + find_package(SFML 3 REQUIRED COMPONENTS Graphics Window System Audio) +endif() + +# Fallback: fetch SFML via FetchContent if not found +if(NOT SFML_FOUND) + message(STATUS "SFML not found, fetching via FetchContent...") + FetchContent_Declare( + SFML + GIT_REPOSITORY https://github.com/SFML/SFML.git + GIT_TAG 3.0.0 + ) + FetchContent_MakeAvailable(SFML) +endif() + +# Link SFML libraries to the executable +target_link_libraries(EcoSimEngine PRIVATE + SFML::Graphics + SFML::Window + SFML::System + SFML::Audio +) + +# OpenGL needed for ImGui-SFML +if(WIN32) + target_link_libraries(EcoSimEngine PRIVATE opengl32) +endif() + +# --------------------- +# Copy config and resources to output +# --------------------- +# Ensures executable has access to config files and assets for both Debug/Release +foreach(CONFIG_TYPE Debug Release) + file(COPY ${CMAKE_SOURCE_DIR}/config DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CONFIG_TYPE}) + file(COPY ${CMAKE_SOURCE_DIR}/resources DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CONFIG_TYPE}) +endforeach() + +# --------------------- +# Compiler warning settings +# --------------------- +if(MSVC) + target_compile_options(EcoSimEngine PRIVATE /W4 /permissive-) # High warning level, disable permissive MSVC +else() + target_compile_options(EcoSimEngine PRIVATE -Wall -Wextra -pedantic) # Enable common warnings for GCC/Clang +endif() + +# --------------------- +# Post-build DLL copy (Windows only) +# --------------------- +# Ensures SFML DLLs are available next to the executable +if(WIN32 AND SFML_FOUND) + function(copy_sfml_dlls target) + foreach(config Debug Release) + if(config STREQUAL "Debug") + set(dll_suffix "-d") + else() + set(dll_suffix "") + endif() + + foreach(lib graphics window system audio) + set(dll "${SFML_DIR}/../../../bin/sfml-${lib}-3${dll_suffix}.dll") + if(EXISTS ${dll}) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${dll} $ + COMMENT "Copying SFML ${lib} DLL for ${config}" + ) + endif() + endforeach() + endforeach() + endfunction() + + copy_sfml_dlls(EcoSimEngine) +endif() + +# --------------------- +# Debug defines +# --------------------- +# Allows code to conditionally compile debug-specific code +if(MSVC) + target_compile_definitions(EcoSimEngine PRIVATE DEBUG_BUILD) +else() + target_compile_definitions(EcoSimEngine PRIVATE DEBUG_BUILD) +endif() +# End of CMakeLists.txt \ No newline at end of file diff --git a/EcoSimEngine.sln b/EcoSimEngine.sln deleted file mode 100644 index c47932d..0000000 --- a/EcoSimEngine.sln +++ /dev/null @@ -1,31 +0,0 @@ -īģŋ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35931.197 d17.13 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EcoSimEngine", "EcoSimEngine.vcxproj", "{004B7F30-AAB7-C613-E5BE-07B367293AC4}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Debug|x64.ActiveCfg = Debug|x64 - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Debug|x64.Build.0 = Debug|x64 - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Debug|x86.ActiveCfg = Debug|Win32 - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Debug|x86.Build.0 = Debug|Win32 - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Release|x64.ActiveCfg = Release|x64 - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Release|x64.Build.0 = Release|x64 - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Release|x86.ActiveCfg = Release|Win32 - {004B7F30-AAB7-C613-E5BE-07B367293AC4}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9360ED07-C92B-49DB-81EF-71014CC6EC51} - EndGlobalSection -EndGlobal diff --git a/EcoSimEngine.vcxproj b/EcoSimEngine.vcxproj deleted file mode 100644 index 1d05047..0000000 --- a/EcoSimEngine.vcxproj +++ /dev/null @@ -1,203 +0,0 @@ -īģŋ - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 17.0 - {004B7F30-AAB7-C613-E5BE-07B367293AC4} - Win32Proj - - - - Application - true - v143 - - - Application - false - v143 - - - Application - true - v143 - - - Application - false - v143 - - - - - - - - - - - - - - - - - - - - - true - - - true - - - true - - - true - - - - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - Level3 - - - true - Windows - - - - - _DEBUG;_WINDOWS;%(PreprocessorDefinitions) - Level3 - stdcpp20 - $(ProjectDir)include;D:\SFML-3.0.0\include;$(ProjectDir)external\;$(ProjectDir)external\imgui;$(ProjectDir)external\imgui-sfml;%(AdditionalIncludeDirectories) - - - true - Console - D:\SFML-3.0.0\lib - sfml-graphics-d.lib;sfml-window-d.lib;sfml-system-d.lib;sfml-audio-d.lib;%(AdditionalDependencies);opengl32.lib - - - - - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - Level3 - - - true - Windows - true - true - - - - - NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - Level3 - stdcpp20 - $(ProjectDir)include;D:\SFML-3.0.0\include;$(ProjectDir)external\;$(ProjectDir)external\imgui;$(ProjectDir)external\imgui-sfml;%(AdditionalIncludeDirectories) - - - true - Console - true - true - D:\SFML-3.0.0\lib - sfml-graphics.lib;sfml-window.lib;sfml-system.lib;sfml-audio.lib;%(AdditionalDependencies);opengl32.lib - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/EcoSimEngine.vcxproj.filters b/EcoSimEngine.vcxproj.filters deleted file mode 100644 index 481c455..0000000 --- a/EcoSimEngine.vcxproj.filters +++ /dev/null @@ -1,184 +0,0 @@ -īģŋ - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - - - - - - - - - - - - - - - - - - Resource Files - - - Resource Files - - - \ No newline at end of file diff --git a/README.md b/README.md index 4d2be54..8f5b3b6 100644 --- a/README.md +++ b/README.md @@ -3,45 +3,82 @@ **EcoSimEngine** is a modular ecosystem simulation engine developed in C++20. It leverages SFML 3.0.0 for graphics rendering and nlohmann::json 3.12.0 for configuration management. The engine employs an Entity-Component-System (ECS) architecture to simulate complex interactions within a 2D environment. +--- + ### đŸ“Ļ Features -- **ECS Architecture**: Utilizes components like `CHealth`, `CBehavior`, and `CTransform` to define entity attributes and behaviours. -- **Modular Design**: Easily extendable with additional components and systems. -- **2D Rendering**: Powered by SFML 3.0.0 for efficient graphics handling. -- **JSON Configuration**: Structured configuration files for environment and simulation settings. +- ECS Architecture: Entities are defined by components like `CHealth`, `CBehavior`, and `CTransform`. +- Modular Design: Easily extendable with additional components and systems. +- 2D Rendering: Powered by SFML 3.0.0 for graphics, window, and audio handling. +- JSON Configuration: Structured configuration files for environments, species, and simulation settings. +- Cross-platform: Buildable on Windows, Linux, and macOS via CMake. + +--- ### 🔧 Dependencies -- **C++20**: Ensure your compiler supports C++20 features. -- **[SFML 3.0.0](https://www.sfml-dev.org/download/sfml/3.0.0/)**: For GUI with graphics, window, and audio functionalities. -- **[nlohmann::json 3.12.0](https://github.com/nlohmann/json/releases/tag/v3.12.0)**: For parsing and handling JSON configuration files. - - I decide use `json` file instead of plain `txt` file for configuration, because it is more structured and easier to manage. +- C++20 compiler (e.g. GCC 11+, Clang 12+, MSVC 19.3+) +- [CMake 3.28+](https://cmake.org/download/) +- [SFML 3.0.0](https://www.sfml-dev.org/download/sfml/3.0.0/) (graphics, window, audio) + +> â„šī¸ The following dependencies are **already included in the project** under `external/`, so you do not need to download them separately: +> - ImGui +> - ImGui-SFML +> - nlohmann/json.hpp + +--- ### 🗂 Project Structure -```make +```text EcoSimEngine/ -├── config/ # JSON configuration files -├── include/ # Header files -├── resources/ # Fonts, textures, and other assets -├── src/ # Source files -├── EcoSimEngine.sln # Visual Studio solution file -├── EcoSimEngine.vcxproj # Visual Studio project file -├── LICENSE # Project license -└── README.md # Project documentation +├── cmake/ # CMake package config (EcoSimEngineConfig.cmake.in, etc.) +├── config/ # JSON configuration files +├── external/ # Third-party libraries (imgui, imgui-sfml, nlohmann) +├── include/ # Public headers (EcoSimEngine API) +│ └── EcoSimEngine/ +│ ├── component/ +│ ├── ecs/ +│ ├── event/ +│ ├── gui/ +│ ├── math/ +│ ├── scene/ +│ ├── system/ +│ └── utils/ +├── resources/ # Assets (fonts, sounds, textures, defaults, definitions) +├── src/ # Engine implementation +├── tests/ # Unit / integration tests (optional) +├── examples/ # Example usage apps (optional) +├── CMakeLists.txt # Root CMake build script +├── LICENSE # License file +└── README.md # Project documentation ``` -### 🛠 Installation & Build +--- + +### 🛠 Build & Run 1. Clone the Repository: -``` -git clone -b prototype https://github.com/czlin7/EcoSimEngine.git -cd EcoSimEngine -``` -2. Open the Project: - - **Visual Studio**: Open `EcoSimEngine.sln` - - **Other IDEs**: Open the project using the appropriate project file. -3. Configure Dependencies: - - Ensure SFML 3.0.0 and nlohmann::json 3.12.0 are correctly linked in your project settings. -4. Build the Project: - - Select the desired configuration (`Debug` or `Release`) and build the project. + + ``` + git clone -b prototype https://github.com/czlin7/EcoSimEngine.git + cd EcoSimEngine + ``` + +2. Configure & build with CMake + + ``` + cmake -B build + cmake --build build --config Release + ``` + + - CMake will first try to find SFML installed on your system. + - **If SFML is not found automatically** (common on Windows), open `CMakeLists.txt` and update the line: + ``` + set(SFML_DIR "D:/SFML-3.0.0/lib/cmake/SFML") # update to your SFML installation path + ``` + +3. Run + - Executables will be placed in: `build/bin/Debug/` or `build/bin/Release`, depending on the build configuration. + +--- ### 🤝 Contributing We welcome contributions to EcoSimEngine! To contribute: @@ -52,7 +89,7 @@ We welcome contributions to EcoSimEngine! To contribute: git checkout -b feature/YourFeatureName ``` 3. **Make your changes** in the branch. Follow existing ECS structure and coding style. -4. **Commit your changes** with clear, descriptive messages. Use prefixes like `[feat]:` or `[fix]:`. +4. **Commit your changes** with clear, descriptive messages. Use prefixes like `[feat] ` or `[fix] `. ``` git commit -m "[feat]: Add CBehavior component" ``` @@ -68,4 +105,4 @@ git push origin feature/YourFeatureName ### 📄 License -This project is licensed under the [GPL-3.0 License](LICENSE). +This project is licensed under the [GPL-3.0 License](LICENSE). \ No newline at end of file diff --git a/cmake/EcoSimEngineConfig.cmake.in b/cmake/EcoSimEngineConfig.cmake.in new file mode 100644 index 0000000..7891c25 --- /dev/null +++ b/cmake/EcoSimEngineConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/EcoSimEngineTargets.cmake") + +set(EcoSimEngine_INCLUDE_DIRS "@PACKAGE_INCLUDE_INSTALL_DIR@") diff --git a/include/EcoSimEngine/SimulationEngine.hpp b/include/EcoSimEngine/SimulationEngine.hpp index 8457746..617fe6d 100644 --- a/include/EcoSimEngine/SimulationEngine.hpp +++ b/include/EcoSimEngine/SimulationEngine.hpp @@ -8,7 +8,7 @@ #include "EcoSimEngine/scene/SceneManager.hpp" #include "EcoSimEngine/ecs/Assets.hpp" #include "EcoSimEngine/system/SystemManager.hpp" -#include "EcoSimEngine/GUI/GUIManager.hpp" +#include "EcoSimEngine/gui/GUIManager.hpp" #include "EcoSimEngine/event/EventBus.hpp" struct WindowConfig { diff --git a/src/GUI/GUIManager.cpp b/src/GUI/GUIManager.cpp index 315f9b2..4e58b30 100644 --- a/src/GUI/GUIManager.cpp +++ b/src/GUI/GUIManager.cpp @@ -1,4 +1,4 @@ -#include "EcoSimEngine/GUI/GUIManager.hpp" +#include "EcoSimEngine/gui/GUIManager.hpp" #include "EcoSimEngine/SimulationEngine.hpp" #include "EcoSimEngine/event/Events.hpp" @@ -15,7 +15,7 @@ GUIManager::GUIManager(SimulationEngine* engine) } GUIManager::~GUIManager() { - // don't call shutdown() here — we will call shutdown explicitly when engine stops. + // don't call shutdown() here īŋŊ we will call shutdown explicitly when engine stops. } void GUIManager::init(sf::RenderWindow& window) { @@ -116,7 +116,7 @@ void GUIManager::loaddFonts() { } catch (const std::exception& e) { std::cerr << "GUIManager::loaddFonts error: " << e.what() - << " — falling back to ImGui default font." << std::endl; + << " īŋŊ falling back to ImGui default font." << std::endl; // Guarantee at least one font exists io.Fonts->Clear(); io.Fonts->AddFontDefault(); diff --git a/src/main.cpp b/src/main.cpp index e6db550..f133cab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,19 +1,25 @@ -// Project: Ecosystem Simulation -// SFML version 3.0.0 -// C++ version C++20 -// -// external libraries used: -// - SFML 3.0.0 -// - nlohmann/json 3.12.0 -// - ImGui-SFML 3.0 - +#include #include #include "EcoSimEngine/SimulationEngine.hpp" #include "EcoSimEngine/math/Vec2.hpp" -int main(void) { - SimulationEngine sim("config/config.json"); - sim.run(); +int main() { + try { + std::cout << "[DEBUG] Starting SimulationEngine..." << std::endl; + + SimulationEngine sim("config/config.json"); + std::cout << "[DEBUG] SimulationEngine created successfully." << std::endl; + + sim.run(); + std::cout << "[DEBUG] Simulation run completed successfully." << std::endl; + + } catch (const std::exception& e) { + std::cerr << "[ERROR] Exception caught: " << e.what() << std::endl; + return -1; + } catch (...) { + std::cerr << "[ERROR] Unknown exception caught!" << std::endl; + return -1; + } return 0; -} \ No newline at end of file +} From a58bbde0bc15ab8400c3e04909821379c729d5c2 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:31:20 +0100 Subject: [PATCH 02/12] chore(project): harden build and core foundation --- .github/workflows/ci.yml | 32 +++ .gitignore | 30 +-- CMakeLists.txt | 224 ++++++++---------- README.md | 162 ++++++------- cmake/EcoSimEngineConfig.cmake.in | 5 - imgui.ini | 16 -- .../component/ComponentIndices.hpp | 7 +- .../component/ComponentManager.hpp | 137 +++++++---- .../EcoSimEngine/{GUI => gui}/GUIManager.hpp | 0 include/EcoSimEngine/system/SystemManager.hpp | 78 +++--- src/{GUI => gui}/GUIManager.cpp | 0 src/main.cpp | 29 +-- tests/foundation_tests.cpp | 130 ++++++++++ 13 files changed, 497 insertions(+), 353 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 cmake/EcoSimEngineConfig.cmake.in delete mode 100644 imgui.ini rename include/EcoSimEngine/{GUI => gui}/GUIManager.hpp (100%) rename src/{GUI => gui}/GUIManager.cpp (100%) create mode 100644 tests/foundation_tests.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d03d369 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: ci + +on: + pull_request: + branches: + - develop + - main + push: + branches: + - develop + - main + +jobs: + foundation-tests: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Configure headless foundation tests + run: > + cmake -S . -B build + -DECOSIM_BUILD_APP=OFF + -DBUILD_TESTING=ON + -DCMAKE_BUILD_TYPE=Release + + - name: Build foundation tests + run: cmake --build build --parallel + + - name: Run foundation tests + run: ctest --test-dir build --output-on-failure diff --git a/.gitignore b/.gitignore index d0332e0..5a2a237 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,12 @@ -#------------------------- # Visual Studio -#------------------------- .vs/ *.suo *.user -*.vcxproj -*.vcxproj.filters -#------------------------- -# CMake build directories -#------------------------- +# CMake and build outputs /build/ /out/ -/out/build/ /cmake-build-*/ - CMakeFiles/ CMakeCache.txt CMakeScripts/ @@ -24,15 +16,17 @@ cmake_install.cmake install_manifest.txt compile_commands.json -#------------------------- -# IDE / Editor -#------------------------- +# IDE / editor state +.idea/ +.vscode/ *.code-workspace -*.idea/ -*.vscode/ +CMakeUserPresets.json + +# Runtime-generated files +imgui.ini +/saves/ +*.log -#------------------------- -# OS files -#------------------------- +# OS metadata Thumbs.db -.DS_Store \ No newline at end of file +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt index d76cdd7..2d4a947 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,139 +1,105 @@ -cmake_minimum_required(VERSION 3.28) -project(EcoSimEngine VERSION 0.1.0 LANGUAGES CXX) - -# --------------------- -# C++ standard settings -# --------------------- -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) # Avoid compiler-specific extensions - -# --------------------- -# Output folders -# --------------------- -# All executables will be placed in build/bin, with subfolders per configuration -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/Debug) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/Release) - -# --------------------- -# Source files -# --------------------- -# Glob all project and ImGui/ImGui-SFML source files -file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS src/*.cpp) -file(GLOB IMGUI_SOURCES CONFIGURE_DEPENDS - external/imgui/*.cpp - external/imgui/backends/*.cpp - external/imgui-sfml/*.cpp -) - -# Define executable -add_executable(EcoSimEngine ${PROJECT_SOURCES} ${IMGUI_SOURCES}) - -# --------------------- -# Include directories -# --------------------- -# Use PRIVATE to prevent leaking internal includes to dependents -target_include_directories(EcoSimEngine PRIVATE - include - external - external/imgui - external/imgui-sfml - external/nlohmann -) +cmake_minimum_required(VERSION 3.24) -# --------------------- -# SFML 3 setup -# --------------------- -include(FetchContent) - -# Try to find SFML installed on the system first -find_package(SFML 3 COMPONENTS Graphics Window System Audio QUIET) - -# On Windows, allow manual override of SFML_DIR -if(NOT SFML_FOUND AND WIN32) - set(SFML_DIR "D:/SFML-3.0.0/lib/cmake/SFML") # <-- Update path to local SFML if needed - find_package(SFML 3 REQUIRED COMPONENTS Graphics Window System Audio) -endif() +project(EcoSimEngine VERSION 0.1.0 LANGUAGES CXX) -# Fallback: fetch SFML via FetchContent if not found -if(NOT SFML_FOUND) - message(STATUS "SFML not found, fetching via FetchContent...") - FetchContent_Declare( - SFML - GIT_REPOSITORY https://github.com/SFML/SFML.git - GIT_TAG 3.0.0 +option(ECOSIM_BUILD_APP "Build the EcoSimEngine desktop application" ON) + +function(ecosim_enable_warnings target) + if(MSVC) + target_compile_options(${target} PRIVATE /W4 /permissive-) + else() + target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic) + endif() +endfunction() + +if(ECOSIM_BUILD_APP) + include(FetchContent) + + find_package(SFML 3 QUIET COMPONENTS Graphics Window System Audio) + if(NOT SFML_FOUND) + message(STATUS "SFML 3 not found locally; fetching SFML 3.0.0") + FetchContent_Declare( + SFML + GIT_REPOSITORY https://github.com/SFML/SFML.git + GIT_TAG 3.0.0 + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(SFML) + endif() + + find_package(OpenGL REQUIRED) + + set(ECOSIM_SOURCES + src/main.cpp + src/SimulationEngine.cpp + src/gui/GUIManager.cpp + src/ecs/Assets.cpp + src/scene/Scene.cpp + src/scene/Scene_Menu.cpp + src/scene/Scene_Simulation.cpp ) - FetchContent_MakeAvailable(SFML) -endif() - -# Link SFML libraries to the executable -target_link_libraries(EcoSimEngine PRIVATE - SFML::Graphics - SFML::Window - SFML::System - SFML::Audio -) -# OpenGL needed for ImGui-SFML -if(WIN32) - target_link_libraries(EcoSimEngine PRIVATE opengl32) -endif() - -# --------------------- -# Copy config and resources to output -# --------------------- -# Ensures executable has access to config files and assets for both Debug/Release -foreach(CONFIG_TYPE Debug Release) - file(COPY ${CMAKE_SOURCE_DIR}/config DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CONFIG_TYPE}) - file(COPY ${CMAKE_SOURCE_DIR}/resources DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CONFIG_TYPE}) -endforeach() + set(IMGUI_SOURCES + external/imgui/imgui.cpp + external/imgui/imgui_draw.cpp + external/imgui/imgui_tables.cpp + external/imgui/imgui_widgets.cpp + external/imgui-sfml/imgui-SFML.cpp + ) -# --------------------- -# Compiler warning settings -# --------------------- -if(MSVC) - target_compile_options(EcoSimEngine PRIVATE /W4 /permissive-) # High warning level, disable permissive MSVC -else() - target_compile_options(EcoSimEngine PRIVATE -Wall -Wextra -pedantic) # Enable common warnings for GCC/Clang + add_executable(EcoSimEngine ${ECOSIM_SOURCES} ${IMGUI_SOURCES}) + target_compile_features(EcoSimEngine PRIVATE cxx_std_20) + target_include_directories( + EcoSimEngine + PRIVATE + include + external + external/imgui + external/imgui-sfml + external/nlohmann + ) + target_link_libraries( + EcoSimEngine + PRIVATE + SFML::Graphics + SFML::Window + SFML::System + SFML::Audio + OpenGL::GL + ) + target_compile_definitions( + EcoSimEngine + PRIVATE + $<$:DEBUG_BUILD> + ) + ecosim_enable_warnings(EcoSimEngine) + + add_custom_command( + TARGET EcoSimEngine + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/config" + "$/config" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/resources" + "$/resources" + COMMENT "Copying runtime configuration and resources" + ) endif() -# --------------------- -# Post-build DLL copy (Windows only) -# --------------------- -# Ensures SFML DLLs are available next to the executable -if(WIN32 AND SFML_FOUND) - function(copy_sfml_dlls target) - foreach(config Debug Release) - if(config STREQUAL "Debug") - set(dll_suffix "-d") - else() - set(dll_suffix "") - endif() - - foreach(lib graphics window system audio) - set(dll "${SFML_DIR}/../../../bin/sfml-${lib}-3${dll_suffix}.dll") - if(EXISTS ${dll}) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${dll} $ - COMMENT "Copying SFML ${lib} DLL for ${config}" - ) - endif() - endforeach() - endforeach() - endfunction() +include(CTest) - copy_sfml_dlls(EcoSimEngine) -endif() +if(BUILD_TESTING) + add_executable( + EcoSimEngineFoundationTests + tests/foundation_tests.cpp + ) + target_compile_features(EcoSimEngineFoundationTests PRIVATE cxx_std_20) + target_include_directories(EcoSimEngineFoundationTests PRIVATE include) + ecosim_enable_warnings(EcoSimEngineFoundationTests) -# --------------------- -# Debug defines -# --------------------- -# Allows code to conditionally compile debug-specific code -if(MSVC) - target_compile_definitions(EcoSimEngine PRIVATE DEBUG_BUILD) -else() - target_compile_definitions(EcoSimEngine PRIVATE DEBUG_BUILD) + add_test( + NAME foundation + COMMAND EcoSimEngineFoundationTests + ) endif() -# End of CMakeLists.txt \ No newline at end of file diff --git a/README.md b/README.md index 8f5b3b6..210959d 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,102 @@ # EcoSimEngine -![License](https://img.shields.io/github/license/czlin7/EcoSimEngine) -**EcoSimEngine** is a modular ecosystem simulation engine developed in C++20. It leverages SFML 3.0.0 for graphics rendering and nlohmann::json 3.12.0 for configuration management. The engine employs an Entity-Component-System (ECS) architecture to simulate complex interactions within a 2D environment. +![License](https://img.shields.io/github/license/czlin7/EcoSimEngine) ---- +EcoSimEngine is a pre-alpha ecosystem simulation project written in C++20. It currently combines a small Entity-Component-System (ECS), scene management, event dispatch, SFML 3 rendering/audio, Dear ImGui via ImGui-SFML, and JSON-driven simulation resources. -### đŸ“Ļ Features -- ECS Architecture: Entities are defined by components like `CHealth`, `CBehavior`, and `CTransform`. -- Modular Design: Easily extendable with additional components and systems. -- 2D Rendering: Powered by SFML 3.0.0 for graphics, window, and audio handling. -- JSON Configuration: Structured configuration files for environments, species, and simulation settings. -- Cross-platform: Buildable on Windows, Linux, and macOS via CMake. +## Current foundation ---- +Implemented areas include: -### 🔧 Dependencies -- C++20 compiler (e.g. GCC 11+, Clang 12+, MSVC 19.3+) -- [CMake 3.28+](https://cmake.org/download/) -- [SFML 3.0.0](https://www.sfml-dev.org/download/sfml/3.0.0/) (graphics, window, audio) - -> â„šī¸ The following dependencies are **already included in the project** under `external/`, so you do not need to download them separately: -> - ImGui -> - ImGui-SFML -> - nlohmann/json.hpp +- entity, component, and system managers; +- component-signature based system membership; +- menu and simulation scenes; +- movement and AI systems; +- a header-only event bus for engine/GUI commands; +- SFML 3 rendering and audio; +- ImGui-SFML GUI integration; +- JSON configuration, species definitions, and default simulation data; +- CMake-based builds; +- small headless foundation tests for core managers and the event bus. ---- +The project is still under active development. Save/load, simulation behaviour, GUI workflows, and test coverage are not yet complete. -### 🗂 Project Structure -```text -EcoSimEngine/ -├── cmake/ # CMake package config (EcoSimEngineConfig.cmake.in, etc.) -├── config/ # JSON configuration files -├── external/ # Third-party libraries (imgui, imgui-sfml, nlohmann) -├── include/ # Public headers (EcoSimEngine API) -│ └── EcoSimEngine/ -│ ├── component/ -│ ├── ecs/ -│ ├── event/ -│ ├── gui/ -│ ├── math/ -│ ├── scene/ -│ ├── system/ -│ └── utils/ -├── resources/ # Assets (fonts, sounds, textures, defaults, definitions) -├── src/ # Engine implementation -├── tests/ # Unit / integration tests (optional) -├── examples/ # Example usage apps (optional) -├── CMakeLists.txt # Root CMake build script -├── LICENSE # License file -└── README.md # Project documentation -``` +## Requirements ---- +- CMake 3.24+ +- a C++20-capable compiler +- Git when CMake needs to fetch SFML -### 🛠 Build & Run +The repository vendors Dear ImGui, ImGui-SFML, and nlohmann/json. CMake first looks for an installed SFML 3 package and otherwise fetches SFML 3.0.0 during configuration. -1. Clone the Repository: +## Build - ``` - git clone -b prototype https://github.com/czlin7/EcoSimEngine.git - cd EcoSimEngine - ``` +Configure from the repository root: -2. Configure & build with CMake +```bash +cmake -S . -B build +cmake --build build --config Release +``` - ``` - cmake -B build - cmake --build build --config Release - ``` +For single-configuration generators such as Unix Makefiles or Ninja, choose the build type during configuration: - - CMake will first try to find SFML installed on your system. - - **If SFML is not found automatically** (common on Windows), open `CMakeLists.txt` and update the line: - ``` - set(SFML_DIR "D:/SFML-3.0.0/lib/cmake/SFML") # update to your SFML installation path - ``` +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build +``` -3. Run - - Executables will be placed in: `build/bin/Debug/` or `build/bin/Release`, depending on the build configuration. +After the application target is built, CMake copies `config/` and `resources/` next to the executable so the current relative runtime paths continue to work. ---- +## Tests -### 🤝 Contributing -We welcome contributions to EcoSimEngine! To contribute: +Run the foundation tests after configuring: -1. **Fork the repository** to your own GitHub account. -2. **Create a new branch** for your feature or bug fix: -``` -git checkout -b feature/YourFeatureName -``` -3. **Make your changes** in the branch. Follow existing ECS structure and coding style. -4. **Commit your changes** with clear, descriptive messages. Use prefixes like `[feat] ` or `[fix] `. -``` -git commit -m "[feat]: Add CBehavior component" +```bash +ctest --test-dir build -C Release --output-on-failure ``` -5. **Push your branch** to your fork: + +The core tests can also be configured without SFML or the desktop application: + +```bash +cmake -S . -B build-tests \ + -DECOSIM_BUILD_APP=OFF \ + -DBUILD_TESTING=ON \ + -DCMAKE_BUILD_TYPE=Release + +cmake --build build-tests +ctest --test-dir build-tests --output-on-failure ``` -git push origin feature/YourFeatureName + +This headless path is used by CI to validate the dependency-free core managers and event bus. + +## Project structure + +```text +EcoSimEngine/ +├── .github/workflows/ # CI +├── config/ # Runtime JSON configuration +├── external/ # Vendored ImGui, ImGui-SFML, nlohmann/json +├── include/EcoSimEngine/ +│ ├── gui/ +│ ├── component/ +│ ├── ecs/ +│ ├── event/ +│ ├── math/ +│ ├── scene/ +│ ├── system/ +│ └── utils/ +├── resources/ # Assets, defaults, and simulation definitions +├── src/ # Application implementation +├── tests/ # Headless foundation tests +├── CMakeLists.txt +├── LICENSE +└── README.md ``` -6. **Open a Pull Request** against the `prototype` branch of the main repository. - - **Guidelines:** - - Ensure your code builds and passes any existing tests. - - Keep commits small and focused. - - Include comments and documentation for new functionality. +## Development status + +The current goal is to keep the foundation small, understandable, and buildable while extending the simulation incrementally. New work should preserve clear ownership between the engine, ECS managers, scenes, systems, and GUI rather than adding abstraction without a concrete need. + +## License -### 📄 License -This project is licensed under the [GPL-3.0 License](LICENSE). \ No newline at end of file +EcoSimEngine is licensed under the GPL-3.0 License. See `LICENSE`. diff --git a/cmake/EcoSimEngineConfig.cmake.in b/cmake/EcoSimEngineConfig.cmake.in deleted file mode 100644 index 7891c25..0000000 --- a/cmake/EcoSimEngineConfig.cmake.in +++ /dev/null @@ -1,5 +0,0 @@ -@PACKAGE_INIT@ - -include("${CMAKE_CURRENT_LIST_DIR}/EcoSimEngineTargets.cmake") - -set(EcoSimEngine_INCLUDE_DIRS "@PACKAGE_INCLUDE_INSTALL_DIR@") diff --git a/imgui.ini b/imgui.ini deleted file mode 100644 index ee97343..0000000 --- a/imgui.ini +++ /dev/null @@ -1,16 +0,0 @@ -[Window][Debug##Default] -Pos=60,60 -Size=400,400 - -[Window][Status] -Pos=1144,35 -Size=77,54 - -[Window][Simulation Info] -Pos=1057,98 -Size=171,198 - -[Window][##MainMenuBar] -Pos=0,0 -Size=1280,19 - diff --git a/include/EcoSimEngine/component/ComponentIndices.hpp b/include/EcoSimEngine/component/ComponentIndices.hpp index d7c6a4a..ee4a665 100644 --- a/include/EcoSimEngine/component/ComponentIndices.hpp +++ b/include/EcoSimEngine/component/ComponentIndices.hpp @@ -1,9 +1,9 @@ #pragma once + #include #include -constexpr size_t MAX_COMPONENTS = 64; - +constexpr std::size_t MAX_COMPONENTS = 64; constexpr std::size_t COMP_INDEX_CTransform = 0; constexpr std::size_t COMP_INDEX_CSpecies = 1; @@ -12,5 +12,4 @@ constexpr std::size_t COMP_INDEX_CEnergy = 3; constexpr std::size_t COMP_INDEX_CReproductive = 4; constexpr std::size_t COMP_INDEX_CBehavior = 5; - -using Signature = std::bitset; \ No newline at end of file +using Signature = std::bitset; diff --git a/include/EcoSimEngine/component/ComponentManager.hpp b/include/EcoSimEngine/component/ComponentManager.hpp index 285c23f..1ebbacb 100644 --- a/include/EcoSimEngine/component/ComponentManager.hpp +++ b/include/EcoSimEngine/component/ComponentManager.hpp @@ -1,52 +1,99 @@ #pragma once -#include -#include +#include #include +#include +#include +#include +#include class ComponentManager { - struct IStore { virtual ~IStore() = default; }; - template struct Store : IStore { - std::unordered_map data; - }; - - std::unordered_map> stores; - - template Store& ensure() { - auto ti = std::type_index(typeid(T)); - auto it = stores.find(ti); - if (it == stores.end()) { - stores[ti] = std::make_unique>(); - } - return *static_cast*>(stores[ti].get()); - } + struct IStore { + virtual ~IStore() = default; + }; + + template + struct Store final : IStore { + std::unordered_map data; + }; + + std::unordered_map> m_stores; + + template + Store& ensureStore() { + const auto type = std::type_index(typeid(T)); + auto it = m_stores.find(type); + if (it == m_stores.end()) { + it = m_stores.emplace(type, std::make_unique>()).first; + } + return *static_cast*>(it->second.get()); + } + + template + Store* findStore() noexcept { + const auto it = m_stores.find(std::type_index(typeid(T))); + if (it == m_stores.end()) { + return nullptr; + } + return static_cast*>(it->second.get()); + } + + template + const Store* findStore() const noexcept { + const auto it = m_stores.find(std::type_index(typeid(T))); + if (it == m_stores.end()) { + return nullptr; + } + return static_cast*>(it->second.get()); + } public: - template - T& add(std::size_t id, Args&&... args) { - auto& store = ensure(); - auto [it, _] = store.data.emplace(id, T(std::forward(args)...)); - return it->second; - } - - template - bool has(std::size_t id) const { - auto ti = std::type_index(typeid(T)); - auto it = stores.find(ti); - if (it == stores.end()) return false; - auto* s = static_cast*>(it->second.get()); - return s->data.find(id) != s->data.end(); - } - - template T& get(std::size_t id) { - auto& s = ensure(); - return s.data.at(id); - } - - template void remove(std::size_t id) { - auto& s = ensure(); - s.data.erase(id); - } - - void clear() { stores.clear(); } -}; \ No newline at end of file + template + T& add(std::size_t id, Args&&... args) { + auto& store = ensureStore(); + auto [it, inserted] = + store.data.emplace(id, T(std::forward(args)...)); + + if (!inserted) { + throw std::logic_error("Entity already has this component type"); + } + + return it->second; + } + + template + [[nodiscard]] bool has(std::size_t id) const noexcept { + const auto* store = findStore(); + return store != nullptr && store->data.contains(id); + } + + template + T& get(std::size_t id) { + auto* store = findStore(); + if (store == nullptr) { + throw std::out_of_range("Component type is not registered"); + } + return store->data.at(id); + } + + template + const T& get(std::size_t id) const { + const auto* store = findStore(); + if (store == nullptr) { + throw std::out_of_range("Component type is not registered"); + } + return store->data.at(id); + } + + template + void remove(std::size_t id) noexcept { + auto* store = findStore(); + if (store != nullptr) { + store->data.erase(id); + } + } + + void clear() noexcept { + m_stores.clear(); + } +}; diff --git a/include/EcoSimEngine/GUI/GUIManager.hpp b/include/EcoSimEngine/gui/GUIManager.hpp similarity index 100% rename from include/EcoSimEngine/GUI/GUIManager.hpp rename to include/EcoSimEngine/gui/GUIManager.hpp diff --git a/include/EcoSimEngine/system/SystemManager.hpp b/include/EcoSimEngine/system/SystemManager.hpp index 54f1d06..4cac98a 100644 --- a/include/EcoSimEngine/system/SystemManager.hpp +++ b/include/EcoSimEngine/system/SystemManager.hpp @@ -1,68 +1,76 @@ -// File: include/EcoSimEngine/system/SystemManager.hpp #pragma once -#include "EcoSimEngine/system/System.hpp" #include "EcoSimEngine/component/ComponentIndices.hpp" +#include "EcoSimEngine/system/System.hpp" +#include +#include #include #include -#include -#include +#include class SystemManager { - std::unordered_map m_signatures{}; - std::unordered_map> m_systems{}; + std::unordered_map m_signatures; + std::unordered_map> m_systems; + public: SystemManager() { - m_systems.reserve(16); // reserve a small number, TWEAK as needed - m_signatures.reserve(16); + m_systems.reserve(16); + m_signatures.reserve(16); } - - - - // Register a system and return a shared_ptr to it - template + template std::shared_ptr RegisterSystem(Args&&... args) { - std::type_index ti(typeid(T)); - assert(m_systems.find(ti) == m_systems.end() && "Registering system more than once."); + const std::type_index type(typeid(T)); + if (m_systems.contains(type)) { + throw std::logic_error("System type is already registered"); + } + auto system = std::make_shared(std::forward(args)...); - m_systems.emplace(ti, system); - return std::static_pointer_cast(system); + m_systems.emplace(type, system); + return system; } - // Set the signature (component bitset) for a system type - template + template void SetSignature(const Signature& signature) { - std::type_index ti(typeid(T)); - assert(m_systems.find(ti) != m_systems.end() && "SetSignature on unregistered system."); - m_signatures[ti] = signature; + const std::type_index type(typeid(T)); + if (!m_systems.contains(type)) { + throw std::logic_error("Cannot set a signature for an unregistered system"); + } + + m_signatures[type] = signature; } - // Remove entity from all systems when destroyed void EntityDestroyed(EntityId id) { for (auto& [_, system] : m_systems) { - if (system) system->mEntities.erase(id); + system->mEntities.erase(id); } } - // Called when an entity's signature changed (add/remove component) void EntitySignatureChanged(EntityId id, const Signature& entitySignature) { for (auto& [type, system] : m_systems) { - auto sIt = m_signatures.find(type); - if (sIt == m_signatures.end()) continue; // no signature set - const Signature& sSig = sIt->second; - if ((entitySignature & sSig) == sSig) system->mEntities.insert(id); - else system->mEntities.erase(id); + const auto signatureIt = m_signatures.find(type); + if (signatureIt == m_signatures.end()) { + continue; + } + + const Signature& systemSignature = signatureIt->second; + if ((entitySignature & systemSignature) == systemSignature) { + system->mEntities.insert(id); + } else { + system->mEntities.erase(id); + } } } - // Get the registered system of type T (or nullptr if none) - template + template std::shared_ptr GetSystem() { - std::type_index ti(typeid(T)); - auto it = m_systems.find(ti); - if (it == m_systems.end()) return nullptr; + const std::type_index type(typeid(T)); + const auto it = m_systems.find(type); + if (it == m_systems.end()) { + return nullptr; + } + return std::static_pointer_cast(it->second); } }; diff --git a/src/GUI/GUIManager.cpp b/src/gui/GUIManager.cpp similarity index 100% rename from src/GUI/GUIManager.cpp rename to src/gui/GUIManager.cpp diff --git a/src/main.cpp b/src/main.cpp index f133cab..9864250 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,25 +1,20 @@ -#include -#include #include "EcoSimEngine/SimulationEngine.hpp" -#include "EcoSimEngine/math/Vec2.hpp" + +#include +#include +#include int main() { try { - std::cout << "[DEBUG] Starting SimulationEngine..." << std::endl; - - SimulationEngine sim("config/config.json"); - std::cout << "[DEBUG] SimulationEngine created successfully." << std::endl; - - sim.run(); - std::cout << "[DEBUG] Simulation run completed successfully." << std::endl; - - } catch (const std::exception& e) { - std::cerr << "[ERROR] Exception caught: " << e.what() << std::endl; - return -1; + SimulationEngine simulation("config/config.json"); + simulation.run(); + } catch (const std::exception& error) { + std::cerr << "EcoSimEngine failed: " << error.what() << '\n'; + return EXIT_FAILURE; } catch (...) { - std::cerr << "[ERROR] Unknown exception caught!" << std::endl; - return -1; + std::cerr << "EcoSimEngine failed with an unknown error\n"; + return EXIT_FAILURE; } - return 0; + return EXIT_SUCCESS; } diff --git a/tests/foundation_tests.cpp b/tests/foundation_tests.cpp new file mode 100644 index 0000000..40df0db --- /dev/null +++ b/tests/foundation_tests.cpp @@ -0,0 +1,130 @@ +#include "EcoSimEngine/component/ComponentManager.hpp" +#include "EcoSimEngine/component/ComponentIndices.hpp" +#include "EcoSimEngine/event/EventBus.hpp" +#include "EcoSimEngine/system/System.hpp" +#include "EcoSimEngine/system/SystemManager.hpp" + +#include +#include + +namespace { + +struct Health { + int value; + explicit Health(int initialValue) : value(initialValue) {} +}; + +class TransformSystem final : public System {}; +class UnregisteredSystem final : public System {}; + +void require(bool condition, const char* message) { + if (!condition) { + throw std::runtime_error(message); + } +} + +void testComponentManager() { + ComponentManager components; + + require(!components.has(7), "new manager must not contain components"); + + auto& health = components.add(7, 125); + require(health.value == 125, "added component must keep constructor state"); + require(components.has(7), "added component must be discoverable"); + require(&components.get(7) == &health, "get must return stored component"); + + bool duplicateRejected = false; + try { + static_cast(components.add(7, 90)); + } catch (const std::logic_error&) { + duplicateRejected = true; + } + require(duplicateRejected, "duplicate component insertion must be rejected"); + + components.remove(7); + require(!components.has(7), "removed component must no longer exist"); + + bool missingGetRejected = false; + try { + static_cast(components.get(7)); + } catch (const std::out_of_range&) { + missingGetRejected = true; + } + require(missingGetRejected, "getting a missing component must fail explicitly"); +} + +void testSystemManager() { + SystemManager systems; + auto transformSystem = systems.RegisterSystem(); + + Signature required; + required.set(0); + systems.SetSignature(required); + + Signature entitySignature; + systems.EntitySignatureChanged(11, entitySignature); + require(!transformSystem->mEntities.contains(11), + "entity without required components must not join system"); + + entitySignature.set(0); + systems.EntitySignatureChanged(11, entitySignature); + require(transformSystem->mEntities.contains(11), + "matching entity must join system"); + + entitySignature.reset(0); + systems.EntitySignatureChanged(11, entitySignature); + require(!transformSystem->mEntities.contains(11), + "entity must leave system after signature stops matching"); + + bool duplicateRejected = false; + try { + static_cast(systems.RegisterSystem()); + } catch (const std::logic_error&) { + duplicateRejected = true; + } + require(duplicateRejected, "duplicate system registration must fail in release builds too"); + + bool unregisteredSignatureRejected = false; + try { + systems.SetSignature(required); + } catch (const std::logic_error&) { + unregisteredSignatureRejected = true; + } + require(unregisteredSignatureRejected, + "setting a signature for an unregistered system must fail explicitly"); +} + +void testEventBus() { + EventBus bus; + int total = 0; + + const auto first = bus.subscribe([&](const int& value) { + total += value; + }); + bus.subscribe([&](const int& value) { + total += value * 10; + }); + + bus.publish(2); + require(total == 22, "publish must notify all subscribers"); + + bus.unsubscribe(first); + bus.publish(1); + require(total == 32, "unsubscribe must remove only the selected subscription"); +} + +} // namespace + +int main() { + try { + testComponentManager(); + testSystemManager(); + testEventBus(); + } catch (const std::exception& error) { + std::cerr << "foundation test failure: " << error.what() << '\n'; + return 1; + } + + std::cout << "foundation tests passed\n"; + return 0; +} From bdaa25d9052a433d529fb0ce6a80520b63869272 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:33:16 +0100 Subject: [PATCH 03/12] ci: verify full Linux application build --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d03d369..2815bfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,3 +30,36 @@ jobs: - name: Run foundation tests run: ctest --test-dir build --output-on-failure + + linux-app-build: + runs-on: ubuntu-22.04 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install SFML Linux build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + xorg-dev \ + libxrandr-dev \ + libxcursor-dev \ + libxi-dev \ + libudev-dev \ + libflac-dev \ + libvorbis-dev \ + libgl1-mesa-dev \ + libegl1-mesa-dev \ + libdrm-dev \ + libgbm-dev + + - name: Configure full application + run: > + cmake -S . -B build-app + -DBUILD_TESTING=OFF + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_BUILD_TYPE=Release + + - name: Build full application + run: cmake --build build-app --parallel From d4b7d2bedd4f621137c38ffbbd6178aa8db6a613 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:36:45 +0100 Subject: [PATCH 04/12] fix(input): make action formatting portable and safe --- include/EcoSimEngine/ecs/Action.hpp | 89 +++++++++++++++++------------ 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/include/EcoSimEngine/ecs/Action.hpp b/include/EcoSimEngine/ecs/Action.hpp index 00e0822..1f97c05 100644 --- a/include/EcoSimEngine/ecs/Action.hpp +++ b/include/EcoSimEngine/ecs/Action.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include "EcoSimEngine/math/Vec2.hpp" @@ -12,68 +12,85 @@ enum class ActionName { RIGHT, SELECT, BACK, - PAUSE, // pause the game + PAUSE, QUIT_AND_SAVE, - LEFT_CLICK, - MIDDLE_CLICK, - RIGHT_CLICK, - MOUSE_MOVE, - TOGGLE_FOLLOW, // toggle camera follow - TOGGLE_TEXTURE, // toggle drawing textures - TOGGLE_COLLISION, // toggle drawing collision boxes - TOGGLE_GRID, // toggle drawing grid + LEFT_CLICK, + MIDDLE_CLICK, + RIGHT_CLICK, + MOUSE_MOVE, + TOGGLE_FOLLOW, + TOGGLE_TEXTURE, + TOGGLE_COLLISION, + TOGGLE_GRID, NONE }; - enum class ActionType { - START, - END, + START, + END, NONE }; class Action { ActionName m_name{ ActionName::NONE }; ActionType m_type{ ActionType::NONE }; - Vec2f m_pos{}; // default to (0,0) + Vec2f m_pos{}; + + [[nodiscard]] static constexpr std::string_view nameString(ActionName name) noexcept { + switch (name) { + case ActionName::UP: return "UP"; + case ActionName::DOWN: return "DOWN"; + case ActionName::LEFT: return "LEFT"; + case ActionName::RIGHT: return "RIGHT"; + case ActionName::SELECT: return "SELECT"; + case ActionName::BACK: return "BACK"; + case ActionName::PAUSE: return "PAUSE"; + case ActionName::QUIT_AND_SAVE: return "QUIT_AND_SAVE"; + case ActionName::LEFT_CLICK: return "LEFT_CLICK"; + case ActionName::MIDDLE_CLICK: return "MIDDLE_CLICK"; + case ActionName::RIGHT_CLICK: return "RIGHT_CLICK"; + case ActionName::MOUSE_MOVE: return "MOUSE_MOVE"; + case ActionName::TOGGLE_FOLLOW: return "TOGGLE_FOLLOW"; + case ActionName::TOGGLE_TEXTURE: return "TOGGLE_TEXTURE"; + case ActionName::TOGGLE_COLLISION: return "TOGGLE_COLLISION"; + case ActionName::TOGGLE_GRID: return "TOGGLE_GRID"; + case ActionName::NONE: return "NONE"; + } + return "UNKNOWN"; + } + + [[nodiscard]] static constexpr std::string_view typeString(ActionType type) noexcept { + switch (type) { + case ActionType::START: return "START"; + case ActionType::END: return "END"; + case ActionType::NONE: return "NONE"; + } + return "UNKNOWN"; + } public: Action() = default; - /// Constructs an Action with a name, type, and position. Action(ActionName name, ActionType type, Vec2f pos) : m_name{ name }, m_type{ type }, m_pos{ pos } { } - /// Constructs an Action with name and type, position defaults to (0,0). Action(ActionName name, ActionType type) : Action{ name, type, Vec2f{} } { } - /// Constructs an Action with name and position, type defaults to NONE. Action(ActionName name, Vec2f pos) : Action{ name, ActionType::NONE, pos } { } - // Getters - [[nodiscard]] const ActionName name() const noexcept { return m_name; } - [[nodiscard]] const ActionType type() const noexcept { return m_type; } + [[nodiscard]] ActionName name() const noexcept { return m_name; } + [[nodiscard]] ActionType type() const noexcept { return m_type; } [[nodiscard]] const Vec2f& pos() const noexcept { return m_pos; } - // Convert to string for debugging/logging - [[nodiscard]] std::string toString() const noexcept { - static constexpr const char* NameStrings[] = { - "UP", "DOWN", "LEFT", "RIGHT", "SELECT", "BACK", "NONE", - "LEFT_CLICK","MIDDLE_CLICK","RIGHT_CLICK","MOUSE_MOVE", - }; - static constexpr const char* TypeStrings[] = { - "START", "END", "NONE" - }; - - return std::format("{} {} {} {}", - NameStrings[static_cast(m_name)], - TypeStrings[static_cast(m_type)], - static_cast(m_pos.x), - static_cast(m_pos.y)); + [[nodiscard]] std::string toString() const { + return std::string{nameString(m_name)} + " " + + std::string{typeString(m_type)} + " " + + std::to_string(static_cast(m_pos.x)) + " " + + std::to_string(static_cast(m_pos.y)); } -}; \ No newline at end of file +}; From 884e388fa5281273e1940f6e5bbd4edb9ad55785 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:38:35 +0100 Subject: [PATCH 05/12] fix(build): normalize simulation header dependencies --- .../EcoSimEngine/scene/Scene_Simulation.hpp | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/include/EcoSimEngine/scene/Scene_Simulation.hpp b/include/EcoSimEngine/scene/Scene_Simulation.hpp index 6687b26..7832933 100644 --- a/include/EcoSimEngine/scene/Scene_Simulation.hpp +++ b/include/EcoSimEngine/scene/Scene_Simulation.hpp @@ -1,26 +1,24 @@ #pragma once -#include +#include +#include -#include "SFML/Graphics/Text.hpp" +#include +#include +#include #include "EcoSimEngine/component/Components.hpp" #include "EcoSimEngine/scene/Scene.hpp" -#include "EcoSimEngine/Utils/SpatialHash.hpp" - -#include +#include "EcoSimEngine/utils/SpatialHash.hpp" class Scene_Simulation : public Scene { private: std::string m_simKey; - const std::string m_defaultSimulationPath{ "resources/defaults/default_simulation.json" }; + const std::string m_defaultSimulationPath{ "resources/defaults/default_simulation.json" }; - // simple species -> color map for rendering std::unordered_map m_speciesColors; - - // spatial accel structure + timers - SpatialHash m_spatialHash{ 120.0f }; // default cell size (tweakable) - sf::Clock m_clock; // for timing updates + SpatialHash m_spatialHash{ 120.0f }; + sf::Clock m_clock; protected: bool m_drawTextures{ true }; @@ -28,30 +26,24 @@ class Scene_Simulation : public Scene { bool m_drawGrid{}; bool m_follow{}; - const Vec2f m_gridSize = { 64, 64 }; - //sf::Text m_gridText; - + const Vec2f m_gridSize{ 64.0f, 64.0f }; Vec2f m_mousePos; - // lifecycle void init(const std::string& simulationKey); - void loadSimulation(const std::string& simulationKey); // load a named/save simulation (TODO: implement file loading) - void loadDefaultSimulation(const std::string & defaultSimulationPath); // load default config - void spawnFromJson(const nlohmann::json& simJson); // shared spawn logic + void loadSimulation(const std::string& simulationKey); + void loadDefaultSimulation(const std::string& defaultSimulationPath); + void spawnFromJson(const nlohmann::json& simJson); - // scene callbacks void onEnd() override; void sDoAction(const Action& action) override; void sRender() override; public: - // simKey = empty -> create new simulation from default config - Scene_Simulation(SimulationEngine* simulationEngine, const std::string& simKey = {}); // Constructor takes in specific simulation name + explicit Scene_Simulation( + SimulationEngine* simulationEngine, + const std::string& simKey = {}); void update() override; - - // helpers std::string buildSavePathFromKey(const std::string& key); - - void onGui(); + void onGui() override; }; From eec99b8afc5d9c46663e0bceb1f03f628cc061d6 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:38:44 +0100 Subject: [PATCH 06/12] fix(assets): include complete sound buffer type --- include/EcoSimEngine/ecs/Assets.hpp | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/include/EcoSimEngine/ecs/Assets.hpp b/include/EcoSimEngine/ecs/Assets.hpp index 962a90b..376d232 100644 --- a/include/EcoSimEngine/ecs/Assets.hpp +++ b/include/EcoSimEngine/ecs/Assets.hpp @@ -1,36 +1,36 @@ #pragma once -#include +#include #include #include +#include +#include #include #include -#include - class Assets { - std::unordered_map m_textureMap; - std::unordered_map m_fontMap; - std::unordered_map m_fontPathMap; - std::unordered_map m_soundBuffers; - std::unordered_map> m_soundMap; // store sounds as unique_ptr + std::unordered_map m_textureMap; + std::unordered_map m_fontMap; + std::unordered_map m_fontPathMap; + std::unordered_map m_soundBuffers; + std::unordered_map> m_soundMap; - void addTexture(const std::string& name, const std::string& path); - void addFont(const std::string& name, const std::string& path); // for both sf::Font and std::string FontPath - void addSound(const std::string& name, const std::string& path); + void addTexture(const std::string& name, const std::string& path); + void addFont(const std::string& name, const std::string& path); + void addSound(const std::string& name, const std::string& path); public: Assets(); ~Assets(); - void loadFromFile(const std::string &path); + void loadFromFile(const std::string& path); - [[nodiscard]] const sf::Texture& getTexture(const std::string& name) const; - [[nodiscard]] const sf::Font& getFont(const std::string& name) const; - [[nodiscard]] const std::string& getFontPath(const std::string& name) const; - [[nodiscard]] sf::Sound& getSound(const std::string& name); + [[nodiscard]] const sf::Texture& getTexture(const std::string& name) const; + [[nodiscard]] const sf::Font& getFont(const std::string& name) const; + [[nodiscard]] const std::string& getFontPath(const std::string& name) const; + [[nodiscard]] sf::Sound& getSound(const std::string& name); [[nodiscard]] const std::unordered_map& getTextureMap() const; - [[nodiscard]] std::unordered_map>& getSoundMap(); + [[nodiscard]] std::unordered_map>& getSoundMap(); }; From 36003938155cb8148b5395b883eea0fd023a17c6 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:41:05 +0100 Subject: [PATCH 07/12] ci: verify Windows application build --- .github/workflows/ci.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2815bfb..48331dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Configure headless foundation tests run: > @@ -36,7 +36,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install SFML Linux build dependencies run: | @@ -63,3 +63,19 @@ jobs: - name: Build full application run: cmake --build build-app --parallel + + windows-app-build: + runs-on: windows-latest + + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Configure full application + run: > + cmake -S . -B build-app + -DBUILD_TESTING=OFF + -DBUILD_SHARED_LIBS=OFF + + - name: Build full application + run: cmake --build build-app --config Release --parallel From c043950b96e6ad1459b222f0a423fdb5f877b86d Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:43:22 +0100 Subject: [PATCH 08/12] build: update SFML fallback to 3.1.0 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d4a947..ce620c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,11 +17,11 @@ if(ECOSIM_BUILD_APP) find_package(SFML 3 QUIET COMPONENTS Graphics Window System Audio) if(NOT SFML_FOUND) - message(STATUS "SFML 3 not found locally; fetching SFML 3.0.0") + message(STATUS "SFML 3 not found locally; fetching SFML 3.1.0") FetchContent_Declare( SFML GIT_REPOSITORY https://github.com/SFML/SFML.git - GIT_TAG 3.0.0 + GIT_TAG 3.1.0 GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(SFML) From c72871018d3644c22ccaa249e247f6a906ca96c4 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:43:35 +0100 Subject: [PATCH 09/12] docs: align dependency and CI guidance --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 210959d..469f4ca 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The project is still under active development. Save/load, simulation behaviour, - a C++20-capable compiler - Git when CMake needs to fetch SFML -The repository vendors Dear ImGui, ImGui-SFML, and nlohmann/json. CMake first looks for an installed SFML 3 package and otherwise fetches SFML 3.0.0 during configuration. +The repository vendors Dear ImGui, ImGui-SFML, and nlohmann/json. CMake first looks for an installed SFML 3 package and otherwise fetches SFML 3.1.0 during configuration. ## Build @@ -67,7 +67,7 @@ cmake --build build-tests ctest --test-dir build-tests --output-on-failure ``` -This headless path is used by CI to validate the dependency-free core managers and event bus. +This headless path is used by CI to validate the dependency-free core managers and event bus. CI also builds the complete desktop application on Linux and Windows. ## Project structure From 59c4ef0654fb87d7b853b7186a9827d9241601d3 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:45:11 +0100 Subject: [PATCH 10/12] build: disable unused SFML network module --- CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce620c3..d909559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.24) +cmake_minimum_required(VERSION 3.28) project(EcoSimEngine VERSION 0.1.0 LANGUAGES CXX) @@ -15,9 +15,10 @@ endfunction() if(ECOSIM_BUILD_APP) include(FetchContent) - find_package(SFML 3 QUIET COMPONENTS Graphics Window System Audio) + find_package(SFML 3.1 QUIET COMPONENTS Graphics Window System Audio) if(NOT SFML_FOUND) - message(STATUS "SFML 3 not found locally; fetching SFML 3.1.0") + message(STATUS "SFML 3.1 not found locally; fetching SFML 3.1.0") + set(SFML_BUILD_NETWORK OFF CACHE BOOL "Do not build unused SFML Network module" FORCE) FetchContent_Declare( SFML GIT_REPOSITORY https://github.com/SFML/SFML.git From f643aba759d5c5a3c395352dc202735b2b796547 Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:45:23 +0100 Subject: [PATCH 11/12] docs: require CMake 3.28 for SFML 3.1 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 469f4ca..dce9a4f 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,11 @@ The project is still under active development. Save/load, simulation behaviour, ## Requirements -- CMake 3.24+ +- CMake 3.28+ - a C++20-capable compiler - Git when CMake needs to fetch SFML -The repository vendors Dear ImGui, ImGui-SFML, and nlohmann/json. CMake first looks for an installed SFML 3 package and otherwise fetches SFML 3.1.0 during configuration. +The repository vendors Dear ImGui, ImGui-SFML, and nlohmann/json. CMake first looks for an installed SFML 3.1 package and otherwise fetches SFML 3.1.0 during configuration. Only the SFML modules used by EcoSimEngine are built. ## Build From f2ae9d78ff6cc1048478228febfe13ba08811f3d Mon Sep 17 00:00:00 2001 From: czlin7 <148967184+czlin7@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:47:47 +0100 Subject: [PATCH 12/12] build: use bundled SFML fallback dependencies --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d909559..0a70341 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ if(ECOSIM_BUILD_APP) if(NOT SFML_FOUND) message(STATUS "SFML 3.1 not found locally; fetching SFML 3.1.0") set(SFML_BUILD_NETWORK OFF CACHE BOOL "Do not build unused SFML Network module" FORCE) + set(SFML_USE_SYSTEM_DEPS OFF CACHE BOOL "Use SFML bundled fallback dependencies" FORCE) FetchContent_Declare( SFML GIT_REPOSITORY https://github.com/SFML/SFML.git