Build & Compilation
Makefile Targets
make run is the primary development command. make dev enables hot reload for game logic.
Shader Compilation
GLSL shaders are compiled to SPIR-V using glslc (Vulkan SDK tool):
Input files in native/shaders/, output in build/shaders/:
CMake Configuration
native/CMakeLists.txt builds the shared library:
Output: build/librenderer.dylib
The Makefile runs CMake with CMAKE_EXPORT_COMPILE_COMMANDS=ON and symlinks compile_commands.json to the repo root for IDE intellisense.
Dependencies (installed via Homebrew):
- Vulkan SDK (includes
glslc, Vulkan headers, MoltenVK) - GLFW 3
- GLM
Vendor headers (in native/vendor/, no separate build):
cgltf.h— glTF 2.0 parser (single-header, needs#define CGLTF_IMPLEMENTATIONin one .cpp)stb_truetype.h— TrueType font rasterizer (needs#define STB_TRUETYPE_IMPLEMENTATION)stb_image.h— Image decoder (needs#define STB_IMAGE_IMPLEMENTATION)
Each #define lives in a separate .cpp file: CGLTF_IMPLEMENTATION in scene/mesh.cpp, STB_IMAGE_IMPLEMENTATION in scene/material.cpp, STB_TRUETYPE_IMPLEMENTATION in ui/ui.cpp.
C# Compilation
The Makefile uses two separate file lists — engine code and game logic — combined into VIEWER_CS:
Uses the Mono C# compiler (mcs). All source files are listed explicitly in MANAGED_CS (engine) and GAMELOGIC_CS_FILES (game logic) — there is no automatic file discovery.
The repo includes SaFiEngine.sln (repo root) and managed/SaFiEngine.csproj for C# language server support (autocomplete, go-to-definition, error checking). These files are not used by the build — the Makefile drives compilation via mcs. After cloning, run dotnet restore once to generate managed/obj/project.assets.json, which the language server needs to resolve System.* types.
Dev Mode (Hot Reload)
make dev splits C# into three assemblies for live code reloading:
A FileSystemWatcher in HotReload.cs monitors game_logic/ for .cs changes. On save, it auto-discovers all .cs files in the directory, recompiles GameLogic.dll, and loads the new assembly via reflection. The reload then looks for a Game.Setup(World) method in the new assembly:
- If found — calls
world.Reset()(despawns all entities, clears component stores, clears systems, clears all 8 light slots on the native side, resets entity IDs) then re-invokesGame.Setup(world)from the new assembly. This makes scene changes (new entities, lights, system registration order) take effect immediately. - If not found — falls back to swapping system delegates by name (systems-only reload).
make run and make app are unaffected — they compile everything into a single Viewer.exe with no hot reload code (#if HOT_RELOAD guards).
Runtime Environment
make run sets environment variables before executing:
DYLD_LIBRARY_PATH— locateslibrenderer.dylibandlibjoltc.dylib(inbuild/) and MoltenVK/GLFW (in/opt/homebrew/lib)VK_ICD_FILENAMES— tells the Vulkan loader where to find the MoltenVK ICD. Note: the path is under/etc/not/share/(Homebrew-specific)mono— runs the compiled .NET executable
Adding a new shader: Create the .vert/.frag file in native/shaders/. In the Makefile, add a new SPV variable (e.g., NEW_VERT_SPV), add a compilation rule, and add it to the shaders: dependency list.
Adding a new C++ source file: Add it to add_library(renderer SHARED ...) in native/CMakeLists.txt. CMake will pick it up on next build.
Adding a new C# file: Engine files go in the appropriate managed/ subdirectory (core/, rendering/, physics/, or runtime/) — add the path to MANAGED_CS in the Makefile. Game files go in game_logic/ (systems in game_logic/systems/) — add the path to GAMELOGIC_CS_FILES. Game files are automatically hot-reloadable with make dev. The .csproj discovers files automatically, so no project file update is needed.