Created
March 13, 2026 15:42
-
-
Save waldnercharles/6f65c1ff206d769d4647213fcf0d369c to your computer and use it in GitHub Desktop.
Just a fat archetypal ecs in ~500 loc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| arch_ecs.h — C23 Archetypal ECS, single-header, SoAoS layout | |
| USAGE: | |
| #define ECS_COMPONENTS(X) X(Type, field) ... | |
| #include "arch_ecs.h" | |
| Zero-initialised ecs_world is ready to use immediately. | |
| Components are immutable per entity lifetime (fixed archetype). | |
| SPAWNING | |
| ecs_entity e = ecs_spawn(&world, Position, Velocity, ({ | |
| .position = { 1.f, 2.f }, | |
| .velocity = { 3.f, 4.f }, | |
| })); | |
| // or: spawn then set | |
| ecs_entity e = ecs_spawn(&world, Position, Velocity); | |
| ecs_set_position(&world, e, (Position){ 1.f, 2.f }); | |
| ITERATING | |
| ECS_WITH(&world, position, velocity) { | |
| position->x += velocity->vx; | |
| } | |
| ECS_QUERY(&world, Position, pos, Velocity, vel) { | |
| pos->x += vel->vx; | |
| } | |
| ECS_EACH(&world) { ... } | |
| ECS_WITH(&world, position) | |
| ECS_NONE_OF(velocity) { ... } | |
| HANDLES | |
| ecs_entity is a thin (id, gen) pair — 8 bytes. | |
| ecs_view() returns a fat view with T* per component. | |
| ecs_alive() catches stale handles after kill or slot reuse. | |
| Double-kill is safe. | |
| */ | |
| #include <assert.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #ifndef ECS_MAX_ENTITIES | |
| #define ECS_MAX_ENTITIES 4096 | |
| #endif | |
| #ifndef ECS_MAX_ARCHETYPES | |
| #define ECS_MAX_ARCHETYPES 64 | |
| #endif | |
| #ifndef ECS_CHUNK_SIZE | |
| #define ECS_CHUNK_SIZE 256 | |
| #endif | |
| #ifdef ECS_COMPONENTS | |
| #ifndef ARCH_ECS_H | |
| #define ARCH_ECS_H | |
| /* --- internal variable prefix --------------------------------- */ | |
| #define ECS_P_(name) _ecs_##name | |
| /* --- component IDs and masks (field-name based) --------------- */ | |
| enum | |
| { | |
| #define ECS_ENUM_(T, f) ECS_ID_##f, | |
| ECS_COMPONENTS(ECS_ENUM_) | |
| #undef ECS_ENUM_ | |
| ECS_COMPONENT_COUNT | |
| }; | |
| _Static_assert(ECS_COMPONENT_COUNT <= 64, "max 64 components"); | |
| #define ECS_MASK(f) ((uint64_t)1ull << ECS_ID_##f) | |
| /* clang-format off */ | |
| /* ECS_MASK_ALL(f, ...) — OR of field masks for any number of components. */ | |
| #define ECS_M_EMPTY_() | |
| #define ECS_M_DEFER_(m) m ECS_M_EMPTY_() | |
| #define ECS_EVAL_(...) ECS_E1_(ECS_E1_(ECS_E1_(__VA_ARGS__))) | |
| #define ECS_E1_(...) ECS_E2_(ECS_E2_(ECS_E2_(__VA_ARGS__))) | |
| #define ECS_E2_(...) ECS_E3_(ECS_E3_(ECS_E3_(__VA_ARGS__))) | |
| #define ECS_E3_(...) ECS_E4_(ECS_E4_(ECS_E4_(__VA_ARGS__))) | |
| #define ECS_E4_(...) __VA_ARGS__ | |
| #define ECS_M_FOLD_(f, ...) ECS_MASK(f) __VA_OPT__(| ECS_M_DEFER_(ECS_M_FOLD_C_)()(__VA_ARGS__)) | |
| #define ECS_M_FOLD_C_() ECS_M_FOLD_ | |
| #define ECS_MASK_ALL(...) (ECS_EVAL_(ECS_M_FOLD_(__VA_ARGS__))) | |
| /* ECS_TMASK_TYPE_ — map a type name to its component mask via _Generic. | |
| ECS_TMASK_CASE_ must stay defined (expanded at each call site). */ | |
| #define ECS_TMASK_CASE_(T, f) T: ECS_MASK(f), | |
| #define ECS_TMASK_TYPE_(TypeName) \ | |
| _Generic((TypeName){0}, ECS_COMPONENTS(ECS_TMASK_CASE_) default: (uint64_t)0) | |
| /* clang-format on */ | |
| /* --- entity handle (thin) ------------------------------------- */ | |
| typedef struct | |
| { | |
| int id; | |
| uint16_t gen; | |
| } ecs_entity; | |
| #define ECS_NULL ((ecs_entity){ 0 }) | |
| /* --- entity map ----------------------------------------------- */ | |
| typedef struct | |
| { | |
| int archetype; /* index into world archetypes, -1 = dead */ | |
| int row; | |
| uint16_t gen; | |
| } ecs_entity_info; | |
| /* --- archetype ------------------------------------------------ */ | |
| typedef struct | |
| { | |
| uint64_t mask; | |
| int count, capacity; | |
| int *entity_ids; | |
| #define ECS_ARCH_COL_(T, f) T *f; | |
| ECS_COMPONENTS(ECS_ARCH_COL_) | |
| #undef ECS_ARCH_COL_ | |
| } ecs_archetype; | |
| /* --- spawn descriptor ----------------------------------------- */ | |
| typedef struct | |
| { | |
| #define ECS_DESC_(T, f) T f; | |
| ECS_COMPONENTS(ECS_DESC_) | |
| #undef ECS_DESC_ | |
| } ecs_spawn_desc; | |
| /* --- view (fat handle) ---------------------------------------- */ | |
| typedef struct | |
| { | |
| int id; | |
| uint16_t gen; | |
| #define ECS_VIEW_PTR_(T, f) T *f; | |
| ECS_COMPONENTS(ECS_VIEW_PTR_) | |
| #undef ECS_VIEW_PTR_ | |
| } ecs_view_t; | |
| /* --- world ---------------------------------------------------- */ | |
| typedef struct | |
| { | |
| ecs_entity_info entities[ECS_MAX_ENTITIES]; | |
| int free_list[ECS_MAX_ENTITIES]; | |
| int free_count, hwm, alive_count; | |
| ecs_archetype archetypes[ECS_MAX_ARCHETYPES]; | |
| int archetype_count; | |
| } ecs_world; | |
| /* --- alive check ---------------------------------------------- */ | |
| static inline bool ecs_alive(ecs_world *w, ecs_entity e) | |
| { | |
| return e.id > 0 && e.id < ECS_MAX_ENTITIES && w->entities[e.id].gen == e.gen; | |
| } | |
| /* --- archetype helpers ---------------------------------------- */ | |
| static inline int ecs_find_or_create_archetype_(ecs_world *w, uint64_t mask) | |
| { | |
| for (int a = 0; a < w->archetype_count; a++) | |
| if (w->archetypes[a].mask == mask) return a; | |
| assert(w->archetype_count < ECS_MAX_ARCHETYPES); | |
| int a = w->archetype_count++; | |
| w->archetypes[a].mask = mask; | |
| return a; | |
| } | |
| static inline void ecs_archetype_grow_(ecs_archetype *arch) | |
| { | |
| int new_cap = arch->capacity + ECS_CHUNK_SIZE; | |
| arch->entity_ids = realloc(arch->entity_ids, (size_t)new_cap * sizeof(int)); | |
| #define ECS_GROW_(T, f) \ | |
| if (arch->mask & ECS_MASK(f)) \ | |
| arch->f = realloc(arch->f, (size_t)new_cap * sizeof(T)); | |
| ECS_COMPONENTS(ECS_GROW_) | |
| #undef ECS_GROW_ | |
| arch->capacity = new_cap; | |
| } | |
| /* --- spawn ---------------------------------------------------- */ | |
| static inline ecs_entity ecs_spawn_(ecs_world *w, uint64_t mask, ecs_spawn_desc *desc) | |
| { | |
| int id; | |
| if (w->free_count > 0) { | |
| id = w->free_list[--w->free_count]; | |
| } else { | |
| assert(w->hwm + 1 < ECS_MAX_ENTITIES); | |
| id = ++w->hwm; | |
| } | |
| w->entities[id].gen++; | |
| int ai = ecs_find_or_create_archetype_(w, mask); | |
| ecs_archetype *arch = &w->archetypes[ai]; | |
| if (arch->count == arch->capacity) ecs_archetype_grow_(arch); | |
| int row = arch->count++; | |
| arch->entity_ids[row] = id; | |
| if (desc) { | |
| #define ECS_COPY_(T, f) \ | |
| if (mask & ECS_MASK(f)) arch->f[row] = desc->f; | |
| ECS_COMPONENTS(ECS_COPY_) | |
| #undef ECS_COPY_ | |
| } | |
| w->entities[id].archetype = ai; | |
| w->entities[id].row = row; | |
| w->alive_count++; | |
| return (ecs_entity){ .id = id, .gen = w->entities[id].gen }; | |
| } | |
| /* --- ecs_spawn: type-folding macro with deferred recursion --- */ | |
| // clang-format off | |
| #define ECS_EAT_(...) | |
| #define ECS_UNWRAP_(...) __VA_ARGS__ | |
| /* is-parenthesized detection */ | |
| #define ECS_IS_PAREN_P_(...) ~, 1 | |
| #define ECS_IS_PAREN_CHK_(...) ECS_IS_PAREN_N_(__VA_ARGS__, 0) | |
| #define ECS_IS_PAREN_N_(a, n, ...) n | |
| /* dispatch: parenthesized → spawn with init, else → spawn mask only */ | |
| #define ECS_SPAWN_LAST_(w, mask, x) ECS_SPAWN_SEL_(ECS_IS_PAREN_P_ x)(w, mask, x) | |
| #define ECS_SPAWN_SEL_(...) ECS_SPAWN_SEL2_(__VA_ARGS__, ECS_SPAWN_INIT_, ECS_SPAWN_NOINIT_, ~) | |
| #define ECS_SPAWN_SEL2_(a, b, sel, ...) sel | |
| #define ECS_SPAWN_INIT_(w, mask, init) ecs_spawn_((w), (mask), &(ecs_spawn_desc)ECS_UNWRAP_ init) | |
| #define ECS_SPAWN_NOINIT_(w, mask, x) ecs_spawn_((w), (mask) | ECS_TMASK_TYPE_(x), NULL) | |
| /* recursive fold: accumulate type masks until last arg */ | |
| #define ECS_SPAWN_FOLD_(w, mask, x, ...) \ | |
| __VA_OPT__(ECS_M_DEFER_(ECS_SPAWN_FOLD_C_)()((w), (mask) | ECS_TMASK_TYPE_(x), __VA_ARGS__) ECS_EAT_) \ | |
| (ECS_SPAWN_LAST_((w), (mask), x)) | |
| #define ECS_SPAWN_FOLD_C_() ECS_SPAWN_FOLD_ | |
| /* ecs_spawn: unified macro — with or without init block. | |
| With init: ecs_spawn(&world, Position, Velocity, ({ ... })) | |
| Without init: ecs_spawn(&world, Position, Velocity) */ | |
| #define ecs_spawn(w, ...) ECS_EVAL_(ECS_SPAWN_FOLD_((w), (uint64_t)0, __VA_ARGS__)) | |
| // clang-format on | |
| /* --- kill (swap-remove) --------------------------------------- */ | |
| static inline void ecs_kill(ecs_world *w, ecs_entity e) | |
| { | |
| if (!ecs_alive(w, e)) return; | |
| ecs_entity_info *info = &w->entities[e.id]; | |
| ecs_archetype *arch = &w->archetypes[info->archetype]; | |
| int row = info->row; | |
| int last = arch->count - 1; | |
| if (row != last) { | |
| int swapped_id = arch->entity_ids[last]; | |
| arch->entity_ids[row] = swapped_id; | |
| #define ECS_SWAP_(T, f) \ | |
| if (arch->mask & ECS_MASK(f)) arch->f[row] = arch->f[last]; | |
| ECS_COMPONENTS(ECS_SWAP_) | |
| #undef ECS_SWAP_ | |
| w->entities[swapped_id].row = row; | |
| } | |
| arch->count--; | |
| info->gen++; | |
| info->archetype = -1; | |
| w->free_list[w->free_count++] = e.id; | |
| w->alive_count--; | |
| } | |
| /* --- view ----------------------------------------------------- */ | |
| static inline ecs_view_t ecs_view_at_(ecs_world *w, ecs_archetype *arch, int row) | |
| { | |
| int id = arch->entity_ids[row]; | |
| return (ecs_view_t){ .id = id, | |
| .gen = w->entities[id].gen, | |
| #define ECS_VA_(T, f) .f = (arch->mask & ECS_MASK(f)) ? &arch->f[row] : NULL, | |
| ECS_COMPONENTS(ECS_VA_) | |
| #undef ECS_VA_ | |
| }; | |
| } | |
| static inline ecs_view_t ecs_view(ecs_world *w, ecs_entity e) | |
| { | |
| assert(ecs_alive(w, e)); | |
| ecs_entity_info *info = &w->entities[e.id]; | |
| return ecs_view_at_(w, &w->archetypes[info->archetype], info->row); | |
| } | |
| /* --- per-component accessors ---------------------------------- */ | |
| #define ECS_GET_IMPL_(T, f) \ | |
| static inline T *ecs_get_##f(ecs_world *w, ecs_entity e) \ | |
| { \ | |
| assert(ecs_alive(w, e)); \ | |
| ecs_entity_info *info = &w->entities[e.id]; \ | |
| ecs_archetype *arch = &w->archetypes[info->archetype]; \ | |
| assert(arch->mask & ECS_MASK(f)); \ | |
| return &arch->f[info->row]; \ | |
| } | |
| ECS_COMPONENTS(ECS_GET_IMPL_) | |
| #undef ECS_GET_IMPL_ | |
| #define ECS_SET_IMPL_(T, f) \ | |
| static inline void ecs_set_##f(ecs_world *w, ecs_entity e, T val) \ | |
| { \ | |
| assert(ecs_alive(w, e)); \ | |
| ecs_entity_info *info = &w->entities[e.id]; \ | |
| ecs_archetype *arch = &w->archetypes[info->archetype]; \ | |
| assert(arch->mask & ECS_MASK(f)); \ | |
| arch->f[info->row] = val; \ | |
| } | |
| ECS_COMPONENTS(ECS_SET_IMPL_) | |
| #undef ECS_SET_IMPL_ | |
| /* clang-format off */ | |
| /* ecs_set(world, entity, value) — type-dispatched setter via _Generic. | |
| ECS_GENERIC_SET_ must stay defined; ecs_set expands it at each call site. */ | |
| #define ECS_GENERIC_SET_(T, f) T: ecs_set_##f, | |
| #define ecs_set(w, e, ...) \ | |
| _Generic((__VA_ARGS__), \ | |
| ECS_COMPONENTS(ECS_GENERIC_SET_) \ | |
| default: (void)0 \ | |
| )((w), (e), __VA_ARGS__) | |
| /* clang-format on */ | |
| /* --- queries -------------------------------------------------- */ | |
| #define ecs_has(w, e, f) \ | |
| (ecs_alive((w), (e)) && \ | |
| ((w)->archetypes[(w)->entities[(e).id].archetype].mask & ECS_MASK(f))) | |
| #define ecs_has_all(w, e, ...) \ | |
| (ecs_alive((w), (e)) && \ | |
| (((w)->archetypes[(w)->entities[(e).id].archetype].mask & \ | |
| ECS_MASK_ALL(__VA_ARGS__)) == ECS_MASK_ALL(__VA_ARGS__))) | |
| static inline ecs_entity ecs_first_(ecs_world *w, uint64_t mask) | |
| { | |
| for (int a = 0; a < w->archetype_count; a++) { | |
| ecs_archetype *arch = &w->archetypes[a]; | |
| if ((arch->mask & mask) == mask && arch->count > 0) { | |
| int id = arch->entity_ids[0]; | |
| return (ecs_entity){ .id = id, .gen = w->entities[id].gen }; | |
| } | |
| } | |
| return ECS_NULL; | |
| } | |
| // clang-format off | |
| #define ecs_first(w, ...) ecs_first_((w), ECS_MASK_ALL(__VA_ARGS__)) | |
| /* --- ECS_WITH / ECS_EACH / ECS_NONE_OF ----------------------- */ | |
| /* | |
| ECS_WITH: iterate entities owning ALL listed components. | |
| Injects auto *restrict pointers per queried field. | |
| ECS_EACH: iterate all live entities. Injects ecs_view_t e. | |
| ECS_NONE_OF: chain after ECS_WITH/ECS_EACH to exclude entities | |
| that own ANY of the listed components. | |
| ECS_WITH(&world, position, velocity) { | |
| position->x += velocity->vx; | |
| } | |
| ECS_EACH(&world) { e.position->x = 0; } | |
| ECS_WITH(&world, position) | |
| ECS_NONE_OF(velocity) { ... } | |
| break and continue work correctly in all three. | |
| */ | |
| /* Variable injection: recursive fold generates one-shot for-loops | |
| with *restrict pointers into archetype SoA arrays. */ | |
| #define ECS_IV_ENTRY_(f, ...) \ | |
| for (typeof(ECS_P_(arch)->f[0]) *restrict f = &ECS_P_(arch)->f[ECS_P_(i)]; f; f = NULL) \ | |
| __VA_OPT__(ECS_M_DEFER_(ECS_IV_NEXT_C_)()(__VA_ARGS__)) | |
| #define ECS_IV_NEXT_(f, ...) \ | |
| for (typeof(ECS_P_(arch)->f[0]) *restrict f = &ECS_P_(arch)->f[ECS_P_(i)]; f; f = NULL) \ | |
| __VA_OPT__(ECS_M_DEFER_(ECS_IV_NEXT_C_)()(__VA_ARGS__)) | |
| #define ECS_IV_NEXT_C_() ECS_IV_NEXT_ | |
| #define ECS_INJECT_VARS_(...) ECS_EVAL_(ECS_IV_ENTRY_(__VA_ARGS__)) | |
| #define ECS_WITH(world, ...) \ | |
| for (ecs_world * ECS_P_(w) = (world), *ECS_P_(once) = ECS_P_(w); ECS_P_(once); ECS_P_(once) = NULL) \ | |
| for (int ECS_P_(a) = 0, ECS_P_(brk) = 0; ECS_P_(a) < ECS_P_(w)->archetype_count && !ECS_P_(brk); ECS_P_(a)++) \ | |
| if ((ECS_P_(w)->archetypes[ECS_P_(a)].mask & ECS_MASK_ALL(__VA_ARGS__)) == ECS_MASK_ALL(__VA_ARGS__)) \ | |
| for (ecs_archetype * ECS_P_(arch) = &ECS_P_(w)->archetypes[ECS_P_(a)], *ECS_P_(a_once) = ECS_P_(arch); ECS_P_(a_once); ECS_P_(a_once) = NULL) \ | |
| for (int ECS_P_(i) = 0; ECS_P_(i) < ECS_P_(arch)->count && !ECS_P_(brk); ECS_P_(i)++) \ | |
| ECS_INJECT_VARS_(__VA_ARGS__) \ | |
| for (int ECS_P_(done) = (ECS_P_(brk) = 1, 0); !ECS_P_(done); ECS_P_(done) = 1, ECS_P_(brk) = 0) | |
| #define ECS_EACH(world) \ | |
| for (ecs_world *ECS_P_(w) = (world), *ECS_P_(once) = ECS_P_(w); ECS_P_(once); ECS_P_(once) = NULL) \ | |
| for (int ECS_P_(a) = 0, ECS_P_(brk) = 0; ECS_P_(a) < ECS_P_(w)->archetype_count && !ECS_P_(brk); ECS_P_(a)++) \ | |
| if (ECS_P_(w)->archetypes[ECS_P_(a)].count > 0) \ | |
| for (ecs_archetype *ECS_P_(arch) = &ECS_P_(w)->archetypes[ECS_P_(a)], *ECS_P_(a_once) = ECS_P_(arch); ECS_P_(a_once); ECS_P_(a_once) = NULL) \ | |
| for (int ECS_P_(i) = 0; ECS_P_(i) < ECS_P_(arch)->count && !ECS_P_(brk); ECS_P_(i)++) \ | |
| for (ecs_view_t e = (ECS_P_(brk) = 1, ecs_view_at_(ECS_P_(w), ECS_P_(arch), ECS_P_(i))); e.id; e.id = 0, ECS_P_(brk) = 0) | |
| #define ECS_NONE_OF(...) if (!(ECS_P_(arch)->mask & ECS_MASK_ALL(__VA_ARGS__))) | |
| /* --- ECS_QUERY (flat-pair: Type, name, Type, name, ...) ------- */ | |
| /* | |
| ECS_QUERY(&world, Position, pos, Velocity, vel) { | |
| pos->x += vel->vx; | |
| } | |
| ECS_QUERY(&world, Position, pos) | |
| ECS_QNONE_OF(Velocity) { ... } | |
| break and continue work correctly. | |
| */ | |
| /* Type-to-field-pointer: resolve a type name to its SoA pointer in the | |
| current archetype at the current row. | |
| ECS_TYPE_FIELD_PTR_ must stay defined (expanded at each call site). */ | |
| #define ECS_TYPE_FIELD_PTR_(T, f) T: &ECS_P_(arch)->f[ECS_P_(i)], | |
| #define ECS_RESOLVE_(TypeName) _Generic((TypeName){ 0 }, ECS_COMPONENTS(ECS_TYPE_FIELD_PTR_) default: (void *)0) | |
| /* Flat-pair mask fold: consume (Type, name) pairs, ignore name. */ | |
| #define ECS_PM_FOLD_(T, name, ...) ECS_TMASK_TYPE_(T) __VA_OPT__(| ECS_M_DEFER_(ECS_PM_FOLD_C_)()(__VA_ARGS__)) | |
| #define ECS_PM_FOLD_C_() ECS_PM_FOLD_ | |
| #define ECS_PMASK_ALL(...) (ECS_EVAL_(ECS_PM_FOLD_(__VA_ARGS__))) | |
| /* Flat-pair variable injection: one-shot for-loops with *restrict pointers. */ | |
| #define ECS_PIV_ENTRY_(T, name, ...) \ | |
| for (typeof(*ECS_RESOLVE_(T)) *restrict name = ECS_RESOLVE_(T); name; name = NULL) \ | |
| __VA_OPT__(ECS_M_DEFER_(ECS_PIV_NEXT_C_)()(__VA_ARGS__)) | |
| #define ECS_PIV_NEXT_(T, name, ...) \ | |
| for (typeof(*ECS_RESOLVE_(T)) *restrict name = ECS_RESOLVE_(T); name; name = NULL) \ | |
| __VA_OPT__(ECS_M_DEFER_(ECS_PIV_NEXT_C_)()(__VA_ARGS__)) | |
| #define ECS_PIV_NEXT_C_() ECS_PIV_NEXT_ | |
| #define ECS_PINJECT_VARS_(...) ECS_EVAL_(ECS_PIV_ENTRY_(__VA_ARGS__)) | |
| #define ECS_QUERY(world, ...) \ | |
| for (ecs_world *ECS_P_(w) = (world), *ECS_P_(once) = ECS_P_(w); ECS_P_(once); ECS_P_(once) = NULL) \ | |
| for (int ECS_P_(a) = 0, ECS_P_(brk) = 0; ECS_P_(a) < ECS_P_(w)->archetype_count && !ECS_P_(brk); ECS_P_(a)++) \ | |
| if ((ECS_P_(w)->archetypes[ECS_P_(a)].mask & ECS_PMASK_ALL(__VA_ARGS__)) == ECS_PMASK_ALL(__VA_ARGS__)) \ | |
| for (ecs_archetype *ECS_P_(arch) = &ECS_P_(w)->archetypes[ECS_P_(a)], *ECS_P_(a_once) = ECS_P_(arch); ECS_P_(a_once); ECS_P_(a_once) = NULL) \ | |
| for (int ECS_P_(i) = 0; ECS_P_(i) < ECS_P_(arch)->count && !ECS_P_(brk); ECS_P_(i)++) \ | |
| ECS_PINJECT_VARS_(__VA_ARGS__) \ | |
| for (int ECS_P_(done) = (ECS_P_(brk) = 1, 0); !ECS_P_(done); ECS_P_(done) = 1, ECS_P_(brk) = 0) | |
| /* ECS_QNONE_OF — exclude by type names (single args, not pairs). */ | |
| #define ECS_TMT_FOLD_(T, ...) ECS_TMASK_TYPE_(T) __VA_OPT__(| ECS_M_DEFER_(ECS_TMT_FOLD_C_)()(__VA_ARGS__)) | |
| #define ECS_TMT_FOLD_C_() ECS_TMT_FOLD_ | |
| #define ECS_TMASK_ALL_TYPES(...) (ECS_EVAL_(ECS_TMT_FOLD_(__VA_ARGS__))) | |
| #define ECS_QNONE_OF(...) if (!(ECS_P_(arch)->mask & ECS_TMASK_ALL_TYPES(__VA_ARGS__))) | |
| // | |
| // clang-format on | |
| /* --- cleanup -------------------------------------------------- */ | |
| static inline void ecs_world_destroy(ecs_world *w) | |
| { | |
| for (int a = 0; a < w->archetype_count; a++) { | |
| ecs_archetype *arch = &w->archetypes[a]; | |
| free(arch->entity_ids); | |
| #define ECS_FREE_(T, f) free(arch->f); | |
| ECS_COMPONENTS(ECS_FREE_) | |
| #undef ECS_FREE_ | |
| } | |
| } | |
| #endif /* ARCH_ECS_H */ | |
| #else | |
| #error "Define ECS_COMPONENTS(X) before including arch_ecs.h" | |
| #endif /* ECS_COMPONENTS */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment