c++#pragma once
#include <darkmoon/ecs.hpp>
// Include your component headers#include "../components/render_component.hpp"
#include "../components/physics_component.hpp"
// List all components here (order matters for the bitmask)using GameCMPs = MP::TypeList<
RenderComponent,
PhysicsComponent
>;
// Tags: empty structs used as boolean flags, no data, no SlotMap slotstruct TagPlayer {};
struct TagEnemy {};
// List all tags hereusing GameTAGs = MP::TypeList<
TagPlayer,
TagEnemy
>;
// Instantiate the EntityManager with your typesusing EM = EntityManager<GameCMPs, GameTAGs>;
using Entity = EM::Entity;
C++// components/physics_component.hpp#pragma once
struct PhysicsComponent {
int positionX {}, positionY {};
};
// components/render_component.hpp#pragma once
struct RenderComponent {
char character {};
RenderComponent(const char* ch) : character(*ch) {}
};
// Adding a component to an entity
auto& e = EM.newEntity();
EM.addComponent<PhysicsComponent>(e);
// Checking a component in a system
if (e.hasComponent<PhysicsComponent>()) { /* ... */ }
// Iterating only entities with a specific component
EM.forEach<MP::TypeList<PhysicsComponent>, MP::TypeList<>>([&](Entity& e){
// only PhysicsComponent entities
});
C++// In types.hppstruct TagPlayer {};
struct TagEnemy {};
using GameTAGs = MP::TypeList<TagPlayer, TagEnemy>;
// Adding a tag to an entity
auto& e = EM.newEntity();
EM.addTag<TagPlayer>(e);
// Checking a tag in a system
if (e.hasTag<TagPlayer>()) { /* ... */ }
// Iterating only entities with a specific tag
EM.forEach<MP::TypeList<>, MP::TypeList<TagPlayer>>([&](Entity& e){
// only TagPlayer entities
});
C++// systems/render_system.hpp#pragma once
#include "../utils/types.hpp"
struct RenderSystem {
// Declare which components and tags this system needsusing SYSCMPs = MP::TypeList<RenderComponent>;
using SYSTAGs = MP::TypeList<>;
void update(EM& em) {
em.forEach<SYSCMPs, SYSTAGs>([&](Entity& e, RenderComponent& r) {
// Optionally access other components on the same entity
if (e.hasComponent<PhysicsComponent>()) {
auto& phys = em.getComponent<PhysicsComponent>(e);
// do something
}
std::cout << r.character << "\n";
});
}
};
// main.cpp#include "./systems/render_system.hpp"
int main() {
EM em {};
Entity& e = em.newEntity();
em.addComponent<RenderComponent>(e);
RenderSystem rm {};
while (true) {
rm.update(em);
}
}
newEntity()
Entity&
destroyEntityByID(id)
void
destroyEntities(deathSet)
void
destroyAll()
void
getEntityByID(id)
Entity*
getEntities()
std::span<Entity>
aliveCount()
std::size_t
freeEntities()
std::size_t
getID()
std::size_t
hasComponent<T>()
bool
hasTag<T>()
bool
addComponent<T>(e, args...)
T&
getComponent<T>(e)
T&
destroyComponent<T>(e)
void
addTag<T>(e)
void
destroyTag<T>(e)
void
forEach<CMPs, TAGs>(func)
void
forEachAny<CMPs, TAGs>(func)
void
C++// Safe pattern for destroying entities during iteration
EM::deathSet toDestroy {};
EM.forEach<SYSCMPs, SYSTAGs>([&](Entity& e, PhysicsComponent& p) {
if (p.positionX > 100)
toDestroy.insert(e.getID());
});
EM.destroyEntities(toDestroy); // destroy after the loop
textm_indices [ ] -> indirection table. Each slot holds:
.id --> position in m_data (when occupied)
--> next free slot index (when free, forms the freelist)
.gen --> generation counter; incremented on every alloc/free
m_data [ ] -> packed array of T. Elements 0..m_size-1 are always valid.
Gaps never exist; erase uses swap-and-pop to stay compact.
m_erase [ ] -> reverse map: m_erase[dataIdx] = slotIdx that points here.
Used to fix up m_indices when swap-and-pop relocates the last element.
C++using key_type = struct { index_type id; gen_type gen; };
// push_back returns a keyauto key = slotmap.push_back(MyComponent{});
// Access via key asserts in debug if stale
MyComponent& cmp = slotmap[key];
// Safe validity check before accessif (slotmap.is_valid(key)) {
slotmap[key].value = 42;
}
// Erase increments generation, invalidates all copies of this key
slotmap.erase(key); // returns false if key was already stale
push_back(T&&)
key_type
O(1)
push_back(const T&)
key_type
O(1)
operator[](key)
T&
O(1)
erase(key)
bool
O(1)
is_valid(key)
bool
O(1)
clear()
void
O(N)
begin() / end()
T*
O(1)
size()
std::size_t
O(1)
capacity()
std::size_t
O(1)
textBefore erase(key B): After erase(key B):
m_data: [ A | B | C ] m_data: [ A | C ]
m_erase: [ a | b | c ] m_erase: [ a | c ]
↑ C moved here; m_indices[c].id updated
C++// Compile-time constant wrappertemplate <typename T, T VAL>
struct constant { static constexpr T value{ VAL }; };
struct true_type : constant<bool, true> {};
struct false_type : constant<bool, false> {};
// Type identity helper used as base for "this struct IS type T"template <typename T>
struct type_id { using type = T; };
// Type equalitytemplate <typename T, typename U> struct is_same : false_type {};
template <typename T> struct is_same<T, T> : true_type {};
template <typename T, typename U>
constexpr bool is_same_v{ is_same<T, U>::value };
C++// Base case: N == 0 --> the type is the first in the packtemplate <typename T, typename... Ts>
struct nth_type<0, T, Ts...> : type_id<T> {};
// Recursive case: strip the head, decrement Ntemplate <std::size_t N, typename T, typename... Ts>
struct nth_type<N, T, Ts...> : type_id<nth_type_t<N - 1, Ts...>> {};
// Convenience aliastemplate <std::size_t N, typename... Ts>
using nth_type_t = typename nth_type<N, TypeList<Ts...>>::type;
C++// Match found at head --> position 0template <typename T, typename... Ts>
struct pos<T, T, Ts...> : constant<std::size_t, 0> {};
// No match yet --> 1 + position in the resttemplate <typename T, typename U, typename... Ts>
struct pos<T, U, Ts...> : constant<std::size_t, 1 + pos_v<T, Ts...>> {};
C++template <typename TLIST>
struct cmp_tag_traits {
static_assert(TLIST::size() <= 64, "Component tag list is too large");
// Narrowest uint that fits one bit per typeusing mask_type = smallest_type<TLIST>;
// Number of types registered
consteval static uint8_t size() noexcept { return TLIST::size(); }
// Bit position of a single component or tagtemplate <typename CMPTAG>
consteval static uint8_t id() noexcept {
static_assert(TLIST::template contains<CMPTAG>(), "Component or Tag not found");
return TLIST::template pos<CMPTAG>();
}
// OR together the bits for all types in the querytemplate <typename... Ts>
consteval static mask_type mask() noexcept {
return (0 | ... | (1LL << id<Ts>()));
}
};
// forEach uses it like this (pseudocode):using Traits = cmp_tag_traits<MergedList>;
constexpr auto queryMask = Traits::mask<PhysicsComponent, TagPlayer>();
// compiled down to a single integer constant zero runtime costif ((entity.mask & queryMask) == queryMask) { /* visit */ }
C++// replace_t: reuse the types in a TypeList with a different variadic template// TypeList<A,B,C> --> std::tuple<A,B,C>using SlotmapTuple = replace_t<std::tuple, GameCMPs>;
// forall_insert_template_t: wrap every type in a TypeList with another template// TypeList<A,B,C> --> TypeList<Slotmap<A>, Slotmap<B>, Slotmap<C>>using SlotmapList = forall_insert_template_t<Slotmap, GameCMPs>;
C++// Both const and non-const overloads are provided// Base case (I == tuple size): do nothing// Recursive case: call f on element I, then advance
MP::for_each_in_tuple(m_slotmaps, [](auto& slotmap) {
slotmap.clear(); // resets every component storage in one call
});
constant<T, V>
struct
true_type / false_type
struct
is_same<T,U>
trait
nth_type<N, Ts...>
trait
pos<T, Ts...>
trait
IFT<Cond, T, F>
trait
TypeList<Ts...>
struct
smallest_type<TL>
alias
cmp_tag_traits<TL>
struct
replace_t<New, TL>
alias
forall_insert_template_t<W, TL>
alias
for_each_in_tuple(t, f)
function
TEXT// structure
src/
├── types.hpp
└── main.cpp
C++// types.hpp#include <darkmoon/ecs.hpp>
struct HealthComponent {
int hp { 100 };
};
using GameCMPs = MP::TypeList<HealthComponent>;
using GameTAGs = MP::TypeList<>;
using EM = EntityManager<GameCMPs, GameTAGs>;
using Entity = EM::Entity;
C++// main.cpp#include <iostream>
#include "types.hpp"
int main (){
EM em {};
// Create entity
Entity& player = em.newEntity();
// Add component
auto& health = em.addComponent<HealthComponent>(player);
health.hp = 80;
// Save ID player
std::size_t playerID = player.getID();
std::cout << "Player ID: " << playerID << "\n";
// Check before accessing (good practice in generic systems)
if(player.hasComponent<HealthComponent>()){
auto& h = em.getComponent<HealthComponent>(player);
h.hp -= 10;
std::cout << "Player HP: " << h.hp << "\n";
}
// EntityManager statistics
std::cout << "Living Entities: " << em.aliveCount() << "\n";
return 0;
}
TEXT// output.txt
Player ID: 0
Player HP: 70
Living Entities: 1
TEXT// structure
src/
├── types.hpp
└── main.cpp
C++// types.hpp#include <darkmoon/ecs.hpp>
struct PositionComponent {
float x {}, y {};
};
struct VelocityComponent {
float dx {}, dy {};
};
struct HealthComponent {
int hp { 100 };
};
// The order in the TypeList determines the bit in the internal bitmask.
// Do not change it once fixed without recompiling everything.using GameCMPs = MP::TypeList<
PositionComponent,
VelocityComponent,
HealthComponent
>;
using GameTAGs = MP::TypeList<>;
using EM = EntityManager<GameCMPs, GameTAGs>;
using Entity = EM::Entity;
C++// main.cpp#include <iostream>
#include "types.hpp"
int main (){
EM em {};
// Create entity and add the 3 components
Entity& player = em.newEntity();
auto& pPos = em.addComponent<PositionComponent>(player);
pPos.x = 10.f; pPos.y = 5.f;
auto& pVel = em.addComponent<VelocityComponent>(player);
pVel.dx = 1.f; pVel.dy = 0.5f;
auto& pHp = em.addComponent<HealthComponent>(player);
pHp.hp = 200;
// Enemy without velocity (static)
Entity& enemy = em.newEntity();
em.addComponent<PositionComponent>(enemy).x = 50.f;
em.addComponent<HealthComponent>(enemy);
// Pickup with only position
Entity& pickup = em.newEntity();
em.addComponent<PositionComponent>(pickup) = { 30.f, 20.f };
std::cout << "Living Entities: " << em.aliveCount() << "\n";
// Movement System
std::cout << "-- Movement System --\n";
em.forEach<MP::TypeList<PositionComponent, VelocityComponent>, MP::TypeList<>>(
[](Entity& e, PositionComponent& pos, VelocityComponent& vel) {
pos.x += vel.dx;
pos.y += vel.dy;
std::cout << " - Entity " << e.getID()
<< " move to (" << pos.x << ", " << pos.y << ")\n";
}
);
// Health System
std::cout << "-- Health System --\n";
em.forEach<MP::TypeList<HealthComponent>, MP::TypeList<>>(
[&](Entity& e, HealthComponent& h) {
// Optionally access other components on the same entity
if (e.hasComponent<PositionComponent>()) {
auto& pos = em.getComponent<PositionComponent>(e);
std::cout << " - Entity " << e.getID()
<< " in (" << pos.x << ", " << pos.y << ")"
<< " HP: " << h.hp << "\n";
}
}
);
return 0;
}
TEXT// output.txt
Living Entities: 3
-- Movement System --
- Entity 0 move to (11, 5.5)
-- Health System --
- Entity 0 in (11, 5.5) HP: 200
- Entity 1 in (50, 0) HP: 100
TEXT// structure
src/
├── types.hpp
└── main.cpp
C++// types.hpp#pragma once
#include <darkmoon/ecs.hpp>
struct HealthComponent {
int hp { 100 };
};
struct TagPlayer {};
struct TagEnemy {};
struct TagInvincible {};
using GameCMPs = MP::TypeList<HealthComponent>;
using GameTAGs = MP::TypeList<TagPlayer, TagEnemy, TagInvincible>;
using EM = EntityManager<GameCMPs, GameTAGs>;
using Entity = EM::Entity;
C++// main.cpp#include <iostream>
#include "types.hpp"
static void applyDamage(EM& em, Entity& e, int dmg){
if(e.hasTag<TagInvincible>()){
std::cout << " - Entity " << e.getID() << " is invincible, no damage\n";
return;
}
auto& h = em.getComponent<HealthComponent>(e);
h.hp -= dmg;
std::cout << " - Entity " << e.getID()
<< " takes " << dmg << " damage -> HP: " << h.hp << "\n";
}
int main (){
EM em {};
// Player
Entity& player = em.newEntity();
em.addComponent<HealthComponent>(player).hp = 200;
em.addTag<TagPlayer>(player);
// Enemies
Entity& orc = em.newEntity();
Entity& troll = em.newEntity();
em.addComponent<HealthComponent>(orc);
em.addComponent<HealthComponent>(troll).hp = 300;
em.addTag<TagEnemy>(orc);
em.addTag<TagEnemy>(troll);
// Simulate player pick power-up
std::cout << "-- Player pick power-up --\n";
em.addTag<TagInvincible>(player);
// Damage enemies
std::cout << "-- Damage all enemies (TagEnemy + HealthComponent) --\n";
em.forEach<MP::TypeList<HealthComponent>, MP::TypeList<TagEnemy>>(
[&](Entity& e, HealthComponent&) {
applyDamage(em, e, 25);
}
);
// Damage player
std::cout << "-- Damage player (TagPlayer + HealthComponent) --\n";
em.forEach<MP::TypeList<HealthComponent>, MP::TypeList<TagPlayer>>(
[&](Entity& e, HealthComponent&) {
applyDamage(em, e, 50);
}
);
// Expire power-up
std::cout << "-- Expire power-up --\n";
em.destroyTag<TagInvincible>(player);
// Damage player
std::cout << "-- Damage player (TagPlayer + HealthComponent) --\n";
em.forEach<MP::TypeList<HealthComponent>, MP::TypeList<TagPlayer>>(
[&](Entity& e, HealthComponent&) {
applyDamage(em, e, 50);
}
);
// Final state
std::cout << "-- Final state --\n";
em.forEach<MP::TypeList<HealthComponent>, MP::TypeList<>>(
[&](Entity& e, HealthComponent& h) {
std::string role =
e.hasTag<TagPlayer>() ? "player" :
e.hasTag<TagEnemy>() ? "enemy" : "unknown";
std::cout << " - [" << role << "] entity " << e.getID()
<< " HP: " << h.hp << "\n";
}
);
return 0;
}
C++// combatSystem.hpp#pragma once
#include "../types.hpp"
// Simulates an attack by the player on all enemies.struct CombatSystem {
using SYSCMPs = MP::TypeList<HealthComponent>;
using SYSTAGs = MP::TypeList<TagEnemy>;
static constexpr int ATTACK_DAMAGE { 15 };
void update(EM& em) {
em.forEach<SYSCMPs, SYSTAGs>(
[&](Entity&, HealthComponent& h) {
h.hp -= ATTACK_DAMAGE;
}
);
}
};