MiniECS organises every simulation around three composable layers. A World owns the scene and drives the game loop. Inside the World live Units — discrete entities that represent objects in the simulation. Each Unit holds one or more Modules, self-contained behaviour components that execute logic every frame. This three-layer model keeps scene management, entity identity, and runtime behaviour cleanly separated, making the codebase easy to reason about and extend.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.
The Three Layers
World — Scene Container
The World is the root object of any MiniECS simulation. It owns astd::vector of std::unique_ptr<Unit> and is the only place where Units are created or destroyed. When play() is called on a World, it enters the main game loop and begins ticking the first Unit it finds that has Modules attached.
Unit — Entity
A Unit is a named entity that lives inside a World. On its own, a Unit does nothing — it is a container for Modules. Units are always created throughWorld::createUnit(), which constructs the Unit and immediately transfers ownership to the World’s internal vector. You interact with a Unit by calling addModule(), removeModule(), or the templated getModule<T>() accessor.
Module — Behaviour Component
A Module is a reusable piece of behaviour attached to a Unit. The baseModule class exposes a virtual start() method that is called once per frame tick. Concrete implementations — such as TransformModule and RendererModule — override start() to provide their specific logic. Every Module carries a ModuleType enum value that is used for duplicate-prevention and safe removal.
Hierarchy Diagram
ModuleType.
Unity Analogy
If you are familiar with Unity, the mapping is direct:| MiniECS | Unity equivalent |
|---|---|
World | Scene |
Unit | GameObject |
Module | Component |
The Game Loop
CallingWorld::play() starts the simulation. Internally it works as follows:
- Checks that
worldUnitsis not empty; prints"No units in the scene"and returns if it is. - Iterates over Units in
worldUnitsone at a time. - For each Unit, checks whether its Module list is non-empty. If it is empty, prints
"No units in the scene have modules"and moves to the next Unit. - For the first Unit whose Module list is non-empty, enters a
while (true)loop: callsunit->callModuleStart()(which invokesstart()on every attached Module), then sleeps 16 milliseconds before repeating.
Key Design Decisions
std::unique_ptr ownership — Both World (for Units) and Unit (for Modules) use std::unique_ptr as the element type of their internal vectors. This means memory is managed automatically: destroying a World destroys all its Units, and destroying a Unit destroys all its Modules.
Duplicate Module prevention — Unit::addModule() iterates the existing Module list and compares ModuleType values before inserting. If a matching type is found, the insertion is aborted and a message is printed to stdout. This ensures at most one instance of any given Module type lives on a Unit at any time.
Type-safe getModule<T>() — Retrieving a Module by concrete type is done through a templated method that uses dynamic_cast internally. It returns a raw T* on success or nullptr — with an error message to std::cerr — when no Module of the requested type is present.
getUnitByName() undefined behaviour — World::getUnitByName() iterates worldUnits and returns a reference to the matching std::unique_ptr<Unit>. If no Unit with the requested name exists, the function reaches the end of its body without a return statement, which is undefined behaviour. Always ensure the name you pass exists in the World before calling this method.
Explore Each Layer
World
Scene container, Unit management, and the play() game loop.
Unit
Entity layer — add, remove, and retrieve Modules.
Module
Composable behaviour — override start() to drive logic.