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.

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.

The Three Layers

World — Scene Container

The World is the root object of any MiniECS simulation. It owns a std::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 through World::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 base Module 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

World ("Scene")
└── Unit ("u1")
    ├── RendererModule   (ModuleType::Renderer)
    └── TransformModule  (ModuleType::Transform)
A single World can contain many Units, and each Unit can carry many Modules — but no Unit may hold two Modules of the same ModuleType.

Unity Analogy

If you are familiar with Unity, the mapping is direct:
MiniECSUnity equivalent
WorldScene
UnitGameObject
ModuleComponent
Just as a Unity Scene holds GameObjects that hold Components, a MiniECS World holds Units that hold Modules.

The Game Loop

Calling World::play() starts the simulation. Internally it works as follows:
  1. Checks that worldUnits is not empty; prints "No units in the scene" and returns if it is.
  2. Iterates over Units in worldUnits one at a time.
  3. 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.
  4. For the first Unit whose Module list is non-empty, enters a while (true) loop: calls unit->callModuleStart() (which invokes start() on every attached Module), then sleeps 16 milliseconds before repeating.
Because the while (true) loop never exits, only the first Unit that has Modules attached will ever be ticked. Any Units that appear later in the worldUnits vector are never reached. In the current implementation, place all Modules you want ticked on the first Unit, or design your simulation around a single active Unit.
// Simplified view of World::play()
void MiniECS::World::play() const
{
    if (!worldUnits.empty())
    {
        for (auto& unit : worldUnits)
        {
            if (!unit->getModulesVector().empty())
            {
                while (true)
                {
                    unit->callModuleStart();                             // call start() on each Module
                    std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60 fps
                }
            }
            else
            {
                std::cout << "No units in the scene have modules\n";
            }
        }
    }
    else
    {
        std::cout << "No units in the scene\n";
    }
}

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 preventionUnit::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.
// Retrieve a TransformModule from a unit
auto* transform = scene.getUnitByName("u1")->getModule<MiniECS::TransformModule>();
if (transform)
{
    transform->position = {22.5f, 33.4f};
}
getUnitByName() undefined behaviourWorld::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.

Build docs developers (and LLMs) love