mustache - A fast, modern C++ Entity Component System
April 15, 2025 · View on GitHub
Why mustache
- Super fast component iteration
- Multithreading support out of the box
- Fully runtime: you can describe Components and Systems without using templates
- Has C-API and can be used with other programing languages like ex: mustache-lua
- Has integration with profiler
- Built-in component lifecycle hooks
Introduction
The entity-component-system (also known as ECS) is an architectural pattern used mostly in game development. For further details:
- Entity Systems Wiki
- Evolve Your Hierarchy
- ECS on Wikipedia
- About ECS on github
- Telegram channel about ECS
Code Example
#include <mustache/ecs/ecs.hpp>
int main() {
struct Position {
float x, y, z;
};
struct Velocity {
float x, y, z;
};
mustache::World world;
for (uint32_t i = 0; i < 1000000; ++i) {
(void) world.entities().create<Position, Velocity>();
}
const auto run_mode = mustache::JobRunMode::kCurrentThread; // or kParallel
world.entities().forEach([](Position& pos, const Velocity& dir) {
constexpr float dt = 1.0f / 60.0f;
pos.x += dt * dir.x;
pos.y += dt * dir.y;
pos.z += dt * dir.z;
}, run_mode);
return 0;
}
Requirements
To be able to use mustache, users must provide a full-featured compiler that supports at least C++17.
The requirements below are mandatory to compile the tests:
CMakeversion 3.7 or later.
Overview
Entities
An mustache::Entity is a class wrapping an opaque uint64_t value allocated by the mustache::EntityManager.
Each mustache::Entity has id (identifier of Entity), version (to check if Entity is still alive) and worldId.
Creating an entity is as simple as:
#include <mustache/ecs/ecs.hpp>
mustache::World world;
const auto entity = world.entities().create();
And destroying an entity is done with:
world.entities().destroy(entity); // to destroy while the next world.update()
world.entities().destroyNow(entity); // to destroy right now
Components (entity data)
The general idea of ECS is to have as little logic in components as possible. All logic should be contained in Systems.
But mustache has very weak requirements for struct / class to be a component.
You must provide the following public methods (trivial or not):
- default constructor
- operator=
- destructor
Creating components
As an example, position and direction information might be represented as:
struct Position {
float x, y, z;
};
struct Velocity {
float x, y, z;
};
Assigning components to entities
To associate a Component with a previously created Entity:
world.entities().assign<Position>(entity, 1.0f, 2.0f, 3.0f);
There are two ways to create an Entity with a given set of Components:
world.entities().create<C0, C1, C2>();
world.entities().begin()
.assign<C0>(/*args to create component C0*/)
.assign<C1>(/*args to create component C1*/)
.assign<C2>(/*args to create component C2*/)
.end();
Component version control
You may wish to iterate over only changed components. Mustache has a built-in version control system.
Each time you request a non-const component from an entity, the version is updated.
You can configure granularity by:
world.entities().addChunkSizeFunction<Component0>(1, 32); // version per chunk of 32
world.entities().addChunkSizeFunction<Component1>(1, 1); // version per instance
Component access
world.entities().getComponent<C>(entity); // mutable (and version will be updated)
world.entities().getComponent<const C>(entity); // const access
Returns nullptr if the component is missing or entity is invalid.
Querying entities and their components
world.entities().forEach([](Entity entity, Component0& required0, const Component1& required1, const Component2* optional2) {
// iterate over all entities with required0 and required1
// optional2 may be nullptr if Component2 is missing
});
Alternative: use PerEntityJob:
struct MySuperJob : public PerEntityJob<MySuperJob> {
void operator()(const Component0&, Component1&) {
// iterate over all entities with required0 and required1
}
};
MySuperJob job;
job.run(world);
Supported function arguments
The function passed to EntityManager::forEach or the operator() of a PerEntityJob can accept the following arguments (in any order):
-
Components
- Passed as reference (
T&orconst T&) — required. - Passed as pointer (
T*orconst T*) — optional,nullptrif entity doesn't have the component. - If
const, version tracking is not updated. - If non-const, version is updated.
- Passed as reference (
-
Entity— the current entity. -
World&— world reference. -
JobInvocationIndex— provides per-invocation info:
struct JobInvocationIndex {
ParallelTaskId task_index;
ParallelTaskItemIndexInTask entity_index_in_task;
ParallelTaskGlobalItemIndex entity_index;
ThreadId thread_id;
};
Job customization
Jobs that inherit from BaseJob (or PerEntityJob) can override advanced hooks:
| Function | Description |
|---|---|
checkMask | Mask of components to track changes |
updateMask | Mask of components to mark as updated |
name, nameCStr | Job name string |
extraArchetypeFilterCheck | Additional filter for archetypes |
extraChunkFilterCheck | Additional filter per-chunk |
applyFilter | Full override of entity filter logic |
taskCount | Split job into N tasks |
onTaskBegin / onTaskEnd | Hook per task |
onJobBegin / onJobEnd | Hook before and after whole job |
See the source of BaseJob for more details.
Multithreading
Mustache has built-in multithreading support:
job.run(world, JobRunMode::kParallel);
world.entities().forEach(func, JobRunMode::kParallel);
Component hooks
Mustache allows defining optional functions for components to control their lifecycle:
| Function | When it's called |
|---|---|
static void afterAssign(...) | After assigning the component to an entity |
static void beforeRemove(...) | Before removing the component from an entity |
static void clone(...) | When cloning a component during entity clone |
static void afterClone(...) | After cloning is done, used for finalization (e.g. remap references) |
These functions are automatically detected and invoked by Mustache.
Examples:
- See
example/component_add_remove_events.cppforafterAssignandbeforeRemoveusage. - See
example/clone_entity.cppfor cloning andafterCloneuse.
Component dependencies
Automatically assign dependencies when assigning a component:
world.entities().addDependency<MainComponent, Component0, Component1>();
Systems
struct System2 : public System<System2> {
void onConfigure(World&, SystemConfig& config) override {
config.update_after = {"System0", "System1"};
config.update_before = {"System3"};
}
void onUpdate(World&) override {
// Your logic
}
};
Events
Define:
struct Collision { Entity left, right; };
Emit:
world.events().post(Collision{first, second});
Subscribe:
struct DebugSystem : public System<DebugSystem>, Receiver<Collision> {
void onConfigure(World&, SystemConfig&) override {
world.events().subscribe<Collision>(this);
}
void onEvent(const Collision &event) override {}
};
Lambda-style:
auto sub = event_manager.subscribe<Collision>([](const Collision& event){ });
Integration
add_subdirectory(third_party/mustache)
target_link_libraries(${PROJECT_NAME} mustache)
Performance
Create time:

Update time:

Profiling
Enable with:
cmake -DMUSTACHE_BUILD_WITH_EASY_PROFILER=ON
Then use EasyProfiler viewer:
