Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/raoulkdev/MiniECS/llms.txt

Use this file to discover all available pages before exploring further.

This page walks you through everything you need to get MiniECS running on your machine. By the end you will have cloned the repository, compiled the project with CMake, and executed a working scene that creates a World, attaches a TransformModule and a RendererModule to a Unit, sets a 2D position, and starts the main loop — all in about five minutes.
1

Install Prerequisites

Before you build MiniECS you need three things on your system:
  • C++20 compiler — GCC 10+, Clang 10+, or MSVC 19.29+ all work.
  • CMake ≥ 3.30 — the CMakeLists.txt sets cmake_minimum_required(VERSION 3.30).
  • GLM — the header-only OpenGL Mathematics library. GLM is used by TransformModule for its glm::vec2 position, rotation, and scale fields.
On macOS with Homebrew:
brew install cmake glm
On Ubuntu / Debian:
sudo apt install cmake libglm-dev
TransformModule.h hardcodes the GLM include path as /opt/homebrew/include/glm/glm.hpp (the default Homebrew location on Apple Silicon). If GLM is installed elsewhere on your system you will need to update that path before building. See the Building page for details.
2

Clone the Repository

Clone MiniECS from GitHub and enter the project directory:
git clone https://github.com/raoulkdev/MiniECS.git && cd MiniECS
The repository root contains the CMakeLists.txt and a src/ directory with all source files. There are no submodules or external dependencies to fetch beyond GLM.
3

Configure with CMake

Run CMake from the project root to generate the build files:
cmake .
CMake reads CMakeLists.txt, sets the C++ standard to C++20, and produces a Makefile (or the equivalent for your platform’s default generator) in the current directory. You should see output ending with -- Build files have been written to: .../MiniECS.
4

Build the Project

Compile the project with:
make
This compiles all sources listed in CMakeLists.txtmain.cpp, the Module, Unit, World, RendererModule, and TransformModule translation units — and links them into the MiniECS executable.
5

Run the Executable

Execute the compiled binary:
./MiniECS
You will see the following lines printed during startup, then the program enters its infinite game loop repeating the last two lines every ~16 ms (press Ctrl-C to stop):
Constructed unit, name: u1
Added Renderer
Added Transform
Start / Started rendering...
Update / Transform -> Position: 22.5, 33.4, Rotation: 0, 0, Scale: 0, 0
Start / Started rendering...
Update / Transform -> Position: 22.5, 33.4, Rotation: 0, 0, Scale: 0, 0
...
Each line maps directly to a lifecycle event: the Unit constructor fires first, then the Module base constructor logs each attachment as it is pushed onto the module vector. Once World::play() is called it enters a while(true) loop — invoking callModuleStart() on every unit, which calls start() on each module, then sleeping 16 ms before the next iteration.
6

Understand the Example Code

The complete src/main.cpp that produced the output above is shown here with inline comments explaining each step:
// Imports
#include <memory>
#include "modules/transform/TransformModule.h"
#include "unit/Unit.h"
#include "world/World.h"

// Example use
int main()
{
    // Create World — the top-level scene container
    MiniECS::World scene("Scene");

    // Create a new Unit inside the world
    // Prints: "Constructed unit, name: u1"
    scene.createUnit("u1");

    // Add a RendererModule to the unit
    // Prints: "Added Renderer"
    scene.getUnitByName("u1")->addModule(MiniECS::ModuleType::Renderer);

    // Add a TransformModule to the unit
    // Prints: "Added Transform"
    scene.getUnitByName("u1")->addModule(MiniECS::ModuleType::Transform);

    // Retrieve the TransformModule by type and set its 2D position
    // getModule<T>() uses dynamic_cast internally — returns nullptr on miss
    scene.getUnitByName("u1")->getModule<MiniECS::TransformModule>()->position = {22.5f, 33.4f};

    // Start the scene — enters an infinite while(true) loop, calling
    // callModuleStart() on every unit and sleeping 16 ms between frames
    scene.play();
}
Key points to notice:
  • World::createUnit constructs a Unit with the given name and takes ownership via unique_ptr.
  • addModule(ModuleType::Renderer) and addModule(ModuleType::Transform) each instantiate the corresponding subclass and push it into the unit’s module vector. The Module base constructor prints "Added <TypeName>" for each.
  • getModule<TransformModule>() is the type-safe template accessor. It walks the module vector and returns a raw pointer cast to TransformModule*, or nullptr (and logs to std::cerr) if the module has not been added.
  • Setting position = {22.5f, 33.4f} assigns a glm::vec2 aggregate. rotation and scale remain zero-initialised.
  • play() enters a while(true) loop. It calls callModuleStart() on each unit, which invokes the virtual start() on every attached module, then sleeps for 16 ms. The loop runs forever — stop the program with Ctrl-C.

Modifying the Example

Now that the project builds, open src/main.cpp and experiment. A few things to try:
// Add a second unit
scene.createUnit("u2");
scene.getUnitByName("u2")->addModule(MiniECS::ModuleType::Transform);
scene.getUnitByName("u2")->getModule<MiniECS::TransformModule>()->position = {0.0f, 0.0f};
scene.getUnitByName("u2")->getModule<MiniECS::TransformModule>()->scale    = {1.0f, 1.0f};

// Remove a module at runtime
scene.getUnitByName("u1")->removeModule(MiniECS::ModuleType::Renderer);
Recompile with make after each change and observe how the console output shifts. Because the framework is small, every std::cout and std::cerr call in the source maps directly to a visible output line — making it straightforward to trace execution flow.

Build docs developers (and LLMs) love