From 1f03113e7ce721c1f22d781c25eb3d6e10422acc Mon Sep 17 00:00:00 2001 From: Ben Hoffman Date: Sun, 16 Aug 2026 11:27:26 -0400 Subject: [PATCH] Add fling_add_module so new engine libraries can declare public and private deps. Nothing calls it yet; this lands the helper, generated *API.h template, and Phase 0 notes so later splits do not invent a second CMake pattern. Co-authored-by: Cursor --- CMake/FlingModule.cmake | 143 +++++++++++++++++++++++++ CMake/FlingModuleAPI.h.in | 11 ++ CMakeLists.txt | 1 + FlingEngine/Core/inc/FlingLibExports.h | 17 +++ Skills/testing.md | 2 +- docs/BuildModules.md | 64 +++++------ 6 files changed, 200 insertions(+), 38 deletions(-) create mode 100644 CMake/FlingModule.cmake create mode 100644 CMake/FlingModuleAPI.h.in create mode 100644 FlingEngine/Core/inc/FlingLibExports.h diff --git a/CMake/FlingModule.cmake b/CMake/FlingModule.cmake new file mode 100644 index 00000000..4ef8f87f --- /dev/null +++ b/CMake/FlingModule.cmake @@ -0,0 +1,143 @@ +# Fling build-module helper. +# +# Add a new engine module with a folder (inc/ + src/) and: +# +# fling_add_module(Physics +# PUBLIC_DEPS Core +# PRIVATE_DEPS Gameplay +# ) +# +# Public deps propagate include dirs and link libs to consumers. +# Private deps do not. Modules are SHARED by default; pass STATIC for a +# static library (API macros become empty). +# +# Generated ${NAME}API.h (e.g. GraphicsAPI.h / GRAPHICS_API) is written to +# that module's build dir and is PUBLIC, so only linkers of the module see it. +# +# Do not call this until the module's PUBLIC_DEPS / PRIVATE_DEPS targets +# already exist (bottom-up add_subdirectory order). Cycles are a configure error. + +set(FLING_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "Path to first-party CMake helpers") + +function(fling_add_module NAME) + cmake_parse_arguments(MODULE + "STATIC" + "" + "PUBLIC_DEPS;PRIVATE_DEPS;PUBLIC_LIBS;PRIVATE_LIBS" + ${ARGN} + ) + + if(NOT NAME) + message(FATAL_ERROR "fling_add_module() requires a module name") + endif() + + string(TOUPPER "${NAME}" MODULE_UPPER) + + foreach(dep IN LISTS MODULE_PUBLIC_DEPS MODULE_PRIVATE_DEPS) + if(NOT TARGET "${dep}" AND NOT TARGET "Fling::${dep}") + message(FATAL_ERROR + "fling_add_module(${NAME}): dependency '${dep}' does not exist. " + "Add modules bottom-up (dependencies before dependents).") + endif() + + get_property(_dep_all GLOBAL PROPERTY "FLING_MODULE_${dep}_ALL_DEPS") + if(NAME IN_LIST _dep_all) + message(FATAL_ERROR + "Fling module dependency cycle: ${NAME} -> ${dep} -> ... -> ${NAME}") + endif() + endforeach() + + set(_all_deps ${MODULE_PUBLIC_DEPS} ${MODULE_PRIVATE_DEPS}) + foreach(dep IN LISTS MODULE_PUBLIC_DEPS MODULE_PRIVATE_DEPS) + get_property(_dep_all GLOBAL PROPERTY "FLING_MODULE_${dep}_ALL_DEPS") + list(APPEND _all_deps ${_dep_all}) + endforeach() + list(REMOVE_DUPLICATES _all_deps) + set_property(GLOBAL PROPERTY "FLING_MODULE_${NAME}_ALL_DEPS" "${_all_deps}") + + set(_module_dir "${CMAKE_CURRENT_SOURCE_DIR}") + file(GLOB_RECURSE _module_sources CONFIGURE_DEPENDS + "${_module_dir}/src/*.cpp" + "${_module_dir}/src/*.h" + "${_module_dir}/src/*.hpp" + "${_module_dir}/src/*.inl" + "${_module_dir}/inc/*.h" + "${_module_dir}/inc/*.hpp" + "${_module_dir}/inc/*.inl" + ) + + if(NOT _module_sources) + message(FATAL_ERROR "fling_add_module(${NAME}): no sources under ${_module_dir}/inc or src") + endif() + + if(MODULE_STATIC) + add_library(${NAME} STATIC ${_module_sources}) + else() + add_library(${NAME} SHARED ${_module_sources}) + endif() + + add_library(Fling::${NAME} ALIAS ${NAME}) + + set(_gen_dir "${CMAKE_CURRENT_BINARY_DIR}/Generated") + file(MAKE_DIRECTORY "${_gen_dir}") + configure_file( + "${FLING_CMAKE_DIR}/FlingModuleAPI.h.in" + "${_gen_dir}/${NAME}API.h" + @ONLY + ) + + target_include_directories(${NAME} + PUBLIC + "${_module_dir}/inc" + "${_gen_dir}" + PRIVATE + "${_module_dir}/src" + ) + + target_compile_definitions(${NAME} PRIVATE "FLING_${MODULE_UPPER}_BUILD=1") + if(MODULE_STATIC) + target_compile_definitions(${NAME} PUBLIC "FLING_${MODULE_UPPER}_STATIC=1") + endif() + + set(_public_link) + foreach(dep IN LISTS MODULE_PUBLIC_DEPS) + if(TARGET "Fling::${dep}") + list(APPEND _public_link "Fling::${dep}") + else() + list(APPEND _public_link "${dep}") + endif() + endforeach() + + set(_private_link) + foreach(dep IN LISTS MODULE_PRIVATE_DEPS) + if(TARGET "Fling::${dep}") + list(APPEND _private_link "Fling::${dep}") + else() + list(APPEND _private_link "${dep}") + endif() + endforeach() + + target_link_libraries(${NAME} + PUBLIC + ${_public_link} + ${MODULE_PUBLIC_LIBS} + PRIVATE + ${_private_link} + ${MODULE_PRIVATE_LIBS} + ) + + set_target_properties(${NAME} PROPERTIES + FOLDER "Fling/Modules" + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + ) + + if(MSVC) + foreach(_source IN ITEMS ${_module_sources}) + get_filename_component(_source_path "${_source}" PATH) + string(REPLACE "${CMAKE_SOURCE_DIR}" "" _group_path "${_source_path}") + string(REPLACE "/" "\\" _group_path "${_group_path}") + source_group("${_group_path}" FILES "${_source}") + endforeach() + endif() +endfunction() diff --git a/CMake/FlingModuleAPI.h.in b/CMake/FlingModuleAPI.h.in new file mode 100644 index 00000000..1b104694 --- /dev/null +++ b/CMake/FlingModuleAPI.h.in @@ -0,0 +1,11 @@ +#pragma once + +#include "FlingLibExports.h" + +#if defined(FLING_@MODULE_UPPER@_STATIC) +# define @MODULE_UPPER@_API +#elif defined(FLING_@MODULE_UPPER@_BUILD) +# define @MODULE_UPPER@_API FLING_LIB_EXPORT +#else +# define @MODULE_UPPER@_API FLING_LIB_IMPORT +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 96a90886..4506d7ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ message( STATUS "Cmake mod path: " ${CMAKE_MODULE_PATH} ) # Include any .cmake macros here from the ./Cmake dir include(FlingEngineInc) +include(FlingModule) include(MSVC_PCH) include(FlingCompilerFlag) include (InstallRequiredSystemLibraries) diff --git a/FlingEngine/Core/inc/FlingLibExports.h b/FlingEngine/Core/inc/FlingLibExports.h new file mode 100644 index 00000000..4992752f --- /dev/null +++ b/FlingEngine/Core/inc/FlingLibExports.h @@ -0,0 +1,17 @@ +#pragma once + +/** + * Primitive import/export attributes used by generated per-module *API.h headers. + * Do not use these directly on engine types — use CORE_API, GRAPHICS_API, etc. + */ + +#if defined(_WIN32) +# define FLING_LIB_EXPORT __declspec(dllexport) +# define FLING_LIB_IMPORT __declspec(dllimport) +#elif defined(__GNUC__) && (__GNUC__ >= 4) +# define FLING_LIB_EXPORT __attribute__((visibility("default"))) +# define FLING_LIB_IMPORT +#else +# define FLING_LIB_EXPORT +# define FLING_LIB_IMPORT +#endif diff --git a/Skills/testing.md b/Skills/testing.md index a682e253..ede99fbd 100644 --- a/Skills/testing.md +++ b/Skills/testing.md @@ -2,7 +2,7 @@ Fling Engine uses [Catch2 3](https://github.com/catchorg/Catch2) for first-party tests. The test target is `FlingTests`, built from `FlingTests/src`, and links the -full `FlingEngine` library (plus `Foundation`), so tests can exercise real engine +full `FlingEngine` library, so tests can exercise real engine code, not just isolated units. ## Running tests diff --git a/docs/BuildModules.md b/docs/BuildModules.md index 811f3697..37b0f370 100644 --- a/docs/BuildModules.md +++ b/docs/BuildModules.md @@ -6,7 +6,7 @@ monolithic `FlingEngine` library into Unreal-style **build modules** so a system Vulkan pipeline. This document is the working design for branch `feature/169-engine-modules`. -Implementation has not started; this file is the spec. +Phase 0 (infrastructure + Core tree) is in progress. ## Locked decisions @@ -34,25 +34,20 @@ These are settled. Do not re-open them while implementing. - Root `CMakeLists.txt` configures options, third-party deps, then adds `FlingEngine`, `FlingTests`, and `Sandbox`. -- `FlingEngine/CMakeLists.txt` glob-recurses almost every engine source file - into **one** `add_library(FlingEngine ...)` (static `.a` / `.lib`). -- `CMake/FlingEngineInc.cmake` adds **every** engine `inc/` folder to the - include path of anything that uses Fling. Consumers cannot opt out of Graphics. -- There is already a prototype: `FlingEngine/Foundation` is a **SHARED** library - with its own `FOUNDATION_API` macro. It is a stub (`FoundationClass`) and is - still linked into the monolith. -- DLL export today is a single `FLING_API`, toggled by `FLING_EXPORT` inside - `Utils/inc/pch.h`. That cannot scale to per-module exports. -- `pch.h` is **not actually a precompiled header**. `CMake/MSVC_PCH.cmake` is - included but never called, and nothing uses `target_precompile_headers`. - It is a kitchen-sink include parsed from scratch in every TU. It also - `#define`s `FLING_EXPORT` and `F_ENABLE_LOGGING`. Several **public headers** - include it (`Timing.h`, `Version.h`, `FlingWindow.h`, `UniformBufferObject.h`), - which is the opposite of how PCH is supposed to work. Decision: **delete it**. - -Folder layout already hints at modules (`Core`, `Graphics`, `Gameplay`, -`Resources`, `Utils`, `Platform`, `Editor`, `Foundation`) but CMake and -includes treat them as one target. +- `FlingEngine/CMakeLists.txt` glob-recurses engine sources into **one** + `add_library(FlingEngine ...)` (static `.a` / `.lib`). +- `CMake/FlingEngineInc.cmake` still adds every remaining engine `inc/` folder + to consumers. Isolation via `target_link_libraries` starts in Phase 1. +- `CMake/FlingModule.cmake` provides `fling_add_module()`; it is not used by a + real shared module yet (Phase 1: Core). +- `Utils/` and `Platform/` have been folded into `FlingEngine/Core/`. + `ImGuiInputBinding.hpp` lives under `Graphics/inc/` (not Core). +- Foundation stub DLL has been deleted. +- DLL export today is still a single `FLING_API`, toggled by `FLING_EXPORT` + inside `Core/inc/pch.h`. Per-module `*_API` macros are generated by + `fling_add_module()` once modules exist. +- `pch.h` is **not actually a precompiled header**. Decision: **delete it** + in Phase 1. ## Goal @@ -181,13 +176,10 @@ FlingEngine/Engine/ # created in Phase 3 when Engine.cpp leaves Core Keep `#include "Logger.h"` style. Do not introduce `Core/Logger.h` prefixes. -`Platform/inc/ImGuiInputBinding.hpp` currently lives with Platform and is -included from `LinuxInput.cpp` / `WindowsInput.cpp`. Moving Platform into -Core would put an ImGui/GLFW-native header in Core. **Do not leave it there -as a Core public header.** During the move, place it under Graphics or Editor -(ImGui is a Graphics/Editor concern). Input `.cpp` files that need it will -have to stop living as Core-only, or the ImGui path becomes a hook Graphics -registers. Resolve this in Phase 0/1; do not give Core a hard ImGui include. +`Graphics/inc/ImGuiInputBinding.hpp` used to live under Platform. Input +`.cpp` files still include it while the engine is a monolith; do not move it +back into Core. Phase 1 must replace that include with a Graphics-registered +hook so Core has no ImGui dependency. Until Phase 3, `Engine.h` / `Engine.cpp` / `FlingEngine.h` may still live under `Core/` but they are **not** part of the Core module’s sources once Engine is @@ -344,16 +336,14 @@ Work stays on `feature/169-engine-modules`. Each phase should leave ### Phase 0 — Infrastructure + Core tree -- Add `CMake/FlingModule.cmake` with `fling_add_module()` and - `CMake/FlingModuleAPI.h.in`. -- Add `FlingLibExports.h` in Core. -- Physically move `FlingEngine/Utils/**` and `FlingEngine/Platform/**` into - `FlingEngine/Core/inc` and `FlingEngine/Core/src`. Update CMake include - paths. Delete empty Utils/Platform dirs. -- Delete Foundation (sources, CMake target, `Engine.cpp` include). -- Stop globbing the whole engine tree in a way that still picks up moved - files twice. -- Document how to add a module (this file + a short comment on the macro). +Done: + +- `CMake/FlingModule.cmake` (`fling_add_module()`) and `CMake/FlingModuleAPI.h.in`. +- `FlingLibExports.h` in Core. +- `Utils/` and `Platform/` moved into `FlingEngine/Core/`. +- `ImGuiInputBinding.hpp` moved to `Graphics/inc/`. +- Foundation stub deleted (`Engine.cpp` include and Sandbox/Tests link removed). +- `FLING_ENGINE_INC()` no longer references deleted Utils/Platform include dirs. ### Phase 1 — Core as a real shared library