From 3c4dccf801b919819397d4defa0b79e2c4619d70 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Tue, 13 Dec 2022 13:36:51 +0900 Subject: [PATCH] [ecs] Organize headers and code There's now a main ecs.h file that includes the sub-system headers, removing the need to explicitly include several header files, but the sub-systems are a less cluttered. --- include/QF/Makemodule.am | 2 + include/QF/ecs.h | 95 ++++++++++++++++ include/QF/ecs/component.h | 120 +------------------- include/QF/ecs/entity.h | 128 +++++++++++++++++++++ include/QF/ecs/hierarchy.h | 2 +- include/QF/scene/transform.h | 4 +- include/QF/ui/text.h | 2 +- include/QF/ui/view.h | 3 +- include/scn_internal.h | 2 - libs/ecs/Makemodule.am | 2 + libs/ecs/component.c | 208 +---------------------------------- libs/ecs/ecs.c | 190 ++++++++++++++++++++++++++++++++ libs/ecs/entity.c | 77 +++++++++++++ libs/ecs/hierarchy.c | 5 +- libs/ui/passage.c | 2 +- 15 files changed, 509 insertions(+), 333 deletions(-) create mode 100644 include/QF/ecs.h create mode 100644 include/QF/ecs/entity.h create mode 100644 libs/ecs/ecs.c create mode 100644 libs/ecs/entity.c diff --git a/include/QF/Makemodule.am b/include/QF/Makemodule.am index 4fa305366..07b2d956e 100644 --- a/include/QF/Makemodule.am +++ b/include/QF/Makemodule.am @@ -15,6 +15,7 @@ include_qf = \ include/QF/darray.h \ include/QF/dstring.h \ include/QF/draw.h \ + include/QF/ecs.h \ include/QF/fbsearch.h \ include/QF/gib.h \ include/QF/hash.h \ @@ -77,6 +78,7 @@ include_qf = \ include_qf_ecs = \ include/QF/ecs/component.h \ + include/QF/ecs/entity.h \ include/QF/ecs/hierarchy.h include_qf_gl = \ diff --git a/include/QF/ecs.h b/include/QF/ecs.h new file mode 100644 index 000000000..2958485ba --- /dev/null +++ b/include/QF/ecs.h @@ -0,0 +1,95 @@ +/* + ecs.h + + Entity Component System + + Copyright (C) 2022 Bill Currie + + Author: Bill Currie + Date: 2022/10/07 + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef __QF_ecs_h +#define __QF_ecs_h + +#include +#include + +#include "QF/qtypes.h" +#include "QF/progs.h"//FIXME for PR_RESMAP + +#include "QF/ecs/component.h" +#include "QF/ecs/hierarchy.h" + +/** \defgroup ecs Entity Component System + \ingroup utils +*/ +///@{ + +#define ENT_GROW 1024 +#define COMP_GROW 128 +#define ENT_IDBITS 20 +#define nullent (~0u) + +typedef struct ecs_pool_s { + uint32_t *sparse; // indexed by entity id, holds index to dense/data + uint32_t *dense; // holds entity id + void *data; // component data + uint32_t count; // number of components/dense entity ids + uint32_t max_count; // current capacity for components/entity ids +} ecs_pool_t; + +typedef struct ecs_registry_s { + uint32_t *entities; + uint32_t next; + uint32_t available; + uint32_t num_entities; + uint32_t max_entities; + const component_t *components; + ecs_pool_t *comp_pools; + uint32_t num_components; + PR_RESMAP (hierarchy_t) hierarchies;//FIXME find a better way +} ecs_registry_t; + +ecs_registry_t *ECS_NewRegistry (void); +void ECS_DelRegistry (ecs_registry_t *registry); +void ECS_RegisterComponents (ecs_registry_t *registry, + const component_t *components, uint32_t count); + +#ifndef __compar_d_fn_t_defined +#define __compar_d_fn_t_defined +typedef int (*__compar_d_fn_t)(const void *, const void *, void *); +#endif +void ECS_SortComponentPool (ecs_registry_t *registry, uint32_t component, + __compar_d_fn_t cmp, void *arg); + +uint32_t ECS_NewEntity (ecs_registry_t *registry); +void ECS_DelEntity (ecs_registry_t *registry, uint32_t ent); +void ECS_RemoveEntities (ecs_registry_t *registry, uint32_t component); + +#undef ECSINLINE + +///@} + +#include "QF/ecs/entity.h" + +#endif//__QF_ecs_h diff --git a/include/QF/ecs/component.h b/include/QF/ecs/component.h index 58b5cc7da..302338d9f 100644 --- a/include/QF/ecs/component.h +++ b/include/QF/ecs/component.h @@ -1,7 +1,7 @@ /* component.h - Component management + ECS Component management Copyright (C) 2022 Bill Currie @@ -37,26 +37,11 @@ #include "QF/qtypes.h" #include "QF/progs.h"//FIXME for PR_RESMAP -#include "QF/ecs/hierarchy.h" - -/** \defgroup component Entity Component System +/** \defgroup ecs_component Entity Component System components \ingroup utils */ ///@{ -#define ENT_GROW 1024 -#define COMP_GROW 128 -#define ENT_IDBITS 20 -#define nullent (~0u) - -typedef struct ecs_pool_s { - uint32_t *sparse; // indexed by entity id, holds index to dense/data - uint32_t *dense; // holds entity id - void *data; // component data - uint32_t count; // number of components/dense entity ids - uint32_t max_count; // current capacity for components/entity ids -} ecs_pool_t; - typedef struct component_s { size_t size; void (*create) (void *); @@ -64,18 +49,6 @@ typedef struct component_s { const char *name; } component_t; -typedef struct ecs_registry_s { - uint32_t *entities; - uint32_t next; - uint32_t available; - uint32_t num_entities; - uint32_t max_entities; - const component_t *components; - ecs_pool_t *comp_pools; - uint32_t num_components; - PR_RESMAP (hierarchy_t) hierarchies;//FIXME find a better way -} ecs_registry_t; - #define COMPINLINE GNU89INLINE inline COMPINLINE void Component_ResizeArray (const component_t *component, @@ -96,20 +69,9 @@ COMPINLINE void *Component_CreateElements (const component_t *component, COMPINLINE void Component_DestroyElements (const component_t *component, void *array, uint32_t index, uint32_t count); -COMPINLINE uint32_t Ent_Index (uint32_t id); -COMPINLINE uint32_t Ent_Generation (uint32_t id); -COMPINLINE uint32_t Ent_NextGen (uint32_t id); - -COMPINLINE int ECS_EntValid (uint32_t id, ecs_registry_t *reg); -COMPINLINE int Ent_HasComponent (uint32_t ent, uint32_t comp, - ecs_registry_t *reg); -COMPINLINE void *Ent_GetComponent (uint32_t ent, uint32_t comp, - ecs_registry_t *reg); -COMPINLINE void *Ent_SetComponent (uint32_t ent, uint32_t comp, - ecs_registry_t *registry, const void *data); #undef COMPINLINE -#ifndef IMPLEMENT_COMPONENT_Funcs +#ifndef IMPLEMENT_ECS_COMPONENT_Funcs #define COMPINLINE GNU89INLINE inline #else #define COMPINLINE VISIBLE @@ -181,81 +143,7 @@ Component_DestroyElements (const component_t *component, void *array, } } -COMPINLINE uint32_t -Ent_Index (uint32_t id) -{ - return id & ((1 << ENT_IDBITS) - 1); -} - -COMPINLINE uint32_t -Ent_Generation (uint32_t id) -{ - return id & ~((1 << ENT_IDBITS) - 1); -} - -COMPINLINE uint32_t -Ent_NextGen(uint32_t id) -{ - return id + (1 << ENT_IDBITS); -} - -COMPINLINE int -ECS_EntValid (uint32_t id, ecs_registry_t *reg) -{ - uint32_t ind = Ent_Index (id); - return ind < reg->num_entities && reg->entities[ind] == id; -} - -COMPINLINE int -Ent_HasComponent (uint32_t ent, uint32_t comp, ecs_registry_t *reg) -{ - ecs_pool_t *pool = ®->comp_pools[comp]; - uint32_t ind = pool->sparse[Ent_Index (ent)]; - return ind < pool->count && pool->dense[ind] == ent; -} - -COMPINLINE void * -Ent_GetComponent (uint32_t ent, uint32_t comp, ecs_registry_t *reg) -{ - const component_t *component = ®->components[comp]; - uint32_t ind = reg->comp_pools[comp].sparse[Ent_Index (ent)]; - byte *data = reg->comp_pools[comp].data; - return data + ind * component->size; -} - -ecs_registry_t *ECS_NewRegistry (void); -void ECS_DelRegistry (ecs_registry_t *registry); -void ECS_RegisterComponents (ecs_registry_t *registry, - const component_t *components, uint32_t count); - -#ifndef __compar_d_fn_t_defined -#define __compar_d_fn_t_defined -typedef int (*__compar_d_fn_t)(const void *, const void *, void *); -#endif -void ECS_SortComponentPool (ecs_registry_t *registry, uint32_t component, - __compar_d_fn_t cmp, void *arg); - -uint32_t ECS_NewEntity (ecs_registry_t *registry); -void ECS_DelEntity (ecs_registry_t *registry, uint32_t ent); -void ECS_RemoveEntities (ecs_registry_t *registry, uint32_t component); - -void *Ent_AddComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry); -void Ent_RemoveComponent (uint32_t ent, uint32_t comp, - ecs_registry_t *registry); - -COMPINLINE void * -Ent_SetComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry, - const void *data) -{ - void *dst = Ent_AddComponent (ent, comp, registry); - if (data) { - return Component_CopyElements (®istry->components[comp], - dst, 0, data, 0, 1); - } else { - return Component_CreateElements (®istry->components[comp], - dst, 0, 1); - } -} +#undef COMPINLINE ///@} diff --git a/include/QF/ecs/entity.h b/include/QF/ecs/entity.h new file mode 100644 index 000000000..c0f80c0f5 --- /dev/null +++ b/include/QF/ecs/entity.h @@ -0,0 +1,128 @@ +/* + entity.h + + ECS entity management + + Copyright (C) 2022 Bill Currie + + Author: Bill Currie + Date: 2022/10/07 + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef __QF_ecs_entity_h +#define __QF_ecs_entity_h + +/** \defgroup ecs_entity Entity Component System Entities + \ingroup ecs +*/ +///@{ + +#define ENT_IDBITS 20 +#define nullent (~0u) + +#define ENTINLINE GNU89INLINE inline + +ENTINLINE int ECS_EntValid (uint32_t id, ecs_registry_t *reg); + +ENTINLINE uint32_t Ent_Index (uint32_t id); +ENTINLINE uint32_t Ent_Generation (uint32_t id); +ENTINLINE uint32_t Ent_NextGen (uint32_t id); + +ENTINLINE int Ent_HasComponent (uint32_t ent, uint32_t comp, + ecs_registry_t *reg); +ENTINLINE void *Ent_GetComponent (uint32_t ent, uint32_t comp, + ecs_registry_t *reg); +ENTINLINE void *Ent_SetComponent (uint32_t ent, uint32_t comp, + ecs_registry_t *registry, const void *data); + +#undef ENTINLINE +#ifndef IMPLEMENT_ECS_ENTITY_Funcs +#define ENTINLINE GNU89INLINE inline +#else +#define ENTINLINE VISIBLE +#endif + +ENTINLINE uint32_t +Ent_Index (uint32_t id) +{ + return id & ((1 << ENT_IDBITS) - 1); +} + +ENTINLINE uint32_t +Ent_Generation (uint32_t id) +{ + return id & ~((1 << ENT_IDBITS) - 1); +} + +ENTINLINE uint32_t +Ent_NextGen(uint32_t id) +{ + return id + (1 << ENT_IDBITS); +} + +ENTINLINE int +ECS_EntValid (uint32_t id, ecs_registry_t *reg) +{ + uint32_t ind = Ent_Index (id); + return ind < reg->num_entities && reg->entities[ind] == id; +} + +ENTINLINE int +Ent_HasComponent (uint32_t ent, uint32_t comp, ecs_registry_t *reg) +{ + ecs_pool_t *pool = ®->comp_pools[comp]; + uint32_t ind = pool->sparse[Ent_Index (ent)]; + return ind < pool->count && pool->dense[ind] == ent; +} + +ENTINLINE void * +Ent_GetComponent (uint32_t ent, uint32_t comp, ecs_registry_t *reg) +{ + const component_t *component = ®->components[comp]; + uint32_t ind = reg->comp_pools[comp].sparse[Ent_Index (ent)]; + byte *data = reg->comp_pools[comp].data; + return data + ind * component->size; +} + +void *Ent_AddComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry); +void Ent_RemoveComponent (uint32_t ent, uint32_t comp, + ecs_registry_t *registry); + +ENTINLINE void * +Ent_SetComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry, + const void *data) +{ + void *dst = Ent_AddComponent (ent, comp, registry); + if (data) { + return Component_CopyElements (®istry->components[comp], + dst, 0, data, 0, 1); + } else { + return Component_CreateElements (®istry->components[comp], + dst, 0, 1); + } +} + +#undef ENTINLINE + +///@} + +#endif//__QF_ecs_entity_h diff --git a/include/QF/ecs/hierarchy.h b/include/QF/ecs/hierarchy.h index 4e4ff0551..d26e43a74 100644 --- a/include/QF/ecs/hierarchy.h +++ b/include/QF/ecs/hierarchy.h @@ -1,7 +1,7 @@ /* hierarchy.h - Hierarchy management + ECS Hierarchy management Copyright (C) 2021 Bill Currie diff --git a/include/QF/scene/transform.h b/include/QF/scene/transform.h index 943509590..46468e1d3 100644 --- a/include/QF/scene/transform.h +++ b/include/QF/scene/transform.h @@ -32,10 +32,8 @@ #define __QF_scene_transform_h #include "QF/darray.h" +#include "QF/ecs.h" #include "QF/qtypes.h" -#include "QF/ecs/component.h" -#include "QF/ecs/hierarchy.h" -#include "QF/ecs/hierarchy.h" #include "QF/simd/vec4f.h" #include "QF/simd/mat4f.h" diff --git a/include/QF/ui/text.h b/include/QF/ui/text.h index b898527b4..66ca22db1 100644 --- a/include/QF/ui/text.h +++ b/include/QF/ui/text.h @@ -36,7 +36,7 @@ #include "QF/darray.h" -#include "QF/ecs/component.h" +#include "QF/ecs.h" // These can be converted to hb_direction_t simply by oring with 4 typedef enum { diff --git a/include/QF/ui/view.h b/include/QF/ui/view.h index e2f977264..4562ba0ec 100644 --- a/include/QF/ui/view.h +++ b/include/QF/ui/view.h @@ -31,8 +31,7 @@ #ifndef __QF_ui_view_h #define __QF_ui_view_h -#include "QF/ecs/component.h" -#include "QF/ecs/hierarchy.h" +#include "QF/ecs.h" /** \defgroup console_view Console View Objects \ingroup console diff --git a/include/scn_internal.h b/include/scn_internal.h index 102ad794b..906bef517 100644 --- a/include/scn_internal.h +++ b/include/scn_internal.h @@ -1,8 +1,6 @@ #ifndef __scn_internal_h #define __scn_internal_h -#include "QF/ecs/hierarchy.h" - typedef struct scene_resources_s { } scene_resources_t; diff --git a/libs/ecs/Makemodule.am b/libs/ecs/Makemodule.am index b624d2062..ae9360bbc 100644 --- a/libs/ecs/Makemodule.am +++ b/libs/ecs/Makemodule.am @@ -9,4 +9,6 @@ libs_ecs_libQFecs_la_LIBADD= $(ecs_deps) libs_ecs_libQFecs_la_DEPENDENCIES= $(ecs_deps) libs_ecs_libQFecs_la_SOURCES= \ libs/ecs/component.c \ + libs/ecs/ecs.c \ + libs/ecs/entity.c \ libs/ecs/hierarchy.c diff --git a/libs/ecs/component.c b/libs/ecs/component.c index dd8be1761..c466f6b50 100644 --- a/libs/ecs/component.c +++ b/libs/ecs/component.c @@ -1,7 +1,7 @@ /* - component.c + compoent.c - Component handling + ECS Component management Copyright (C) 2022 Bill Currke @@ -28,205 +28,5 @@ # include "config.h" #endif -#include "QF/heapsort.h" -#include "QF/sys.h" - -#define IMPLEMENT_COMPONENT_Funcs -#include "QF/ecs/component.h" - -VISIBLE ecs_registry_t * -ECS_NewRegistry (void) -{ - ecs_registry_t *reg = calloc (1, sizeof (ecs_registry_t)); - reg->next = Ent_Index (nullent); - return reg; -} - -VISIBLE void -ECS_DelRegistry (ecs_registry_t *registry) -{ - free (registry->entities); - for (uint32_t i = 0; i < registry->num_components; i++) { - free (registry->comp_pools[i].sparse); - free (registry->comp_pools[i].dense); - free (registry->comp_pools[i].data); - } - free (registry->comp_pools); - free (registry); -} - -VISIBLE void -ECS_RegisterComponents (ecs_registry_t *registry, - const component_t *components, uint32_t count) -{ - registry->num_components = count; - registry->components = components; - registry->comp_pools = calloc (count, sizeof (ecs_pool_t)); - size_t size = registry->max_entities * sizeof (uint32_t); - for (uint32_t i = 0; i < registry->num_components; i++) { - registry->comp_pools[i].sparse = malloc (size); - memset (registry->comp_pools[i].sparse, nullent, size); - } -} - -typedef struct { - __compar_d_fn_t cmp; - void *arg; - ecs_pool_t *pool; - const component_t *comp; -} ecs_sort_t; - -static int -ecs_compare (const void *a, const void *b, void *arg) -{ - ecs_sort_t *sortctx = arg; - return sortctx->cmp (a, b, sortctx->arg); -} - -static void -swap_uint32 (uint32_t *a, uint32_t *b) -{ - uint32_t t = *a; - *a = *b; - *b = t; -} - -static void -ecs_swap (void *_a, void *_b, void *arg) -{ - ecs_sort_t *sortctx = arg; - size_t size = sortctx->comp->size; - ecs_pool_t *pool = sortctx->pool; - uint32_t *a = _a; - uint32_t *b = _b; - uint32_t a_ind = a - pool->dense; - uint32_t b_ind = b - pool->dense; - uint32_t a_ent_ind = Ent_Index (pool->dense[a_ind]); - uint32_t b_ent_ind = Ent_Index (pool->dense[b_ind]); - __auto_type a_data = (byte *) pool->data + a_ind * size; - __auto_type b_data = (byte *) pool->data + b_ind * size; - Component_SwapElements (sortctx->comp, a_data, b_data); - swap_uint32 (a, b); - swap_uint32 (&pool->sparse[a_ent_ind], &pool->sparse[b_ent_ind]); -} - -VISIBLE void -ECS_SortComponentPool (ecs_registry_t *registry, uint32_t component, - __compar_d_fn_t cmp, void *arg) -{ - if (component >= registry->num_components) { - Sys_Error ("ECS_SortComponentPool: invalid component: %u", component); - } - ecs_pool_t *pool = ®istry->comp_pools[component]; - if (!pool->count) { - return; - } - __auto_type comp = ®istry->components[component]; - ecs_sort_t sortctx = { .cmp = cmp, .arg = arg, .pool = pool, .comp = comp }; - heapsort_s (pool->dense, pool->count, sizeof (uint32_t), - ecs_compare, ecs_swap, &sortctx); -} - -VISIBLE void * -Ent_AddComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry) -{ - ecs_pool_t *pool = ®istry->comp_pools[comp]; - uint32_t id = Ent_Index (ent); - uint32_t ind = pool->sparse[id]; - if (ind >= pool->count || pool->dense[ind] != ent) { - if (pool->count == pool->max_count) { - pool->max_count += COMP_GROW; - pool->dense = realloc (pool->dense, - pool->max_count * sizeof (uint32_t)); - Component_ResizeArray (®istry->components[comp], &pool->data, - pool->max_count); - } - uint32_t ind = pool->count++; - pool->sparse[id] = ind; - pool->dense[ind] = ent; - } - return Ent_GetComponent (ent, comp, registry); -} - -VISIBLE void -Ent_RemoveComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry) -{ - uint32_t id = Ent_Index (ent); - ecs_pool_t *pool = ®istry->comp_pools[comp]; - uint32_t ind = pool->sparse[id]; - if (ind < pool->count && pool->dense[ind] == ent) { - uint32_t last = pool->count - 1; - Component_DestroyElements (®istry->components[comp], pool->data, - ind, 1); - if (last > ind) { - pool->sparse[Ent_Index (pool->dense[last])] = ind; - pool->dense[ind] = pool->dense[last]; - Component_MoveElements (®istry->components[comp], pool->data, - ind, last, 1); - } - pool->count--; - pool->sparse[id] = nullent; - } -} - -VISIBLE uint32_t -ECS_NewEntity (ecs_registry_t *registry) -{ - uint32_t ent; - if (registry->available) { - registry->available--; - uint32_t next = registry->next; - ent = next | Ent_Generation (registry->entities[next]); - registry->next = Ent_Index (registry->entities[next]); - registry->entities[next] = ent; - } else { - if (registry->num_entities == Ent_Index (nullent)) { - Sys_Error ("ECS_NewEntity: out of entities"); - } - if (registry->num_entities == registry->max_entities) { - registry->max_entities += ENT_GROW; - size_t size = registry->max_entities * sizeof (uint32_t); - registry->entities = realloc (registry->entities, size); - for (uint32_t i = 0; i < registry->num_components; i++) { - uint32_t *sparse = registry->comp_pools[i].sparse; - sparse = realloc (sparse, size); - memset (sparse + registry->max_entities - ENT_GROW, nullent, - ENT_GROW * sizeof (uint32_t)); - registry->comp_pools[i].sparse = sparse; - } - } - ent = registry->num_entities++; - // ent starts out with generation 0 - registry->entities[ent] = ent; - } - return ent; -} - -VISIBLE void -ECS_DelEntity (ecs_registry_t *registry, uint32_t ent) -{ - uint32_t next = registry->next | Ent_NextGen (Ent_Generation (ent)); - uint32_t id = Ent_Index (ent); - registry->entities[id] = next; - registry->next = id; - registry->available++; - - for (uint32_t i = 0; i < registry->num_components; i++) { - Ent_RemoveComponent (ent, i, registry); - } -} - -VISIBLE void -ECS_RemoveEntities (ecs_registry_t *registry, uint32_t component) -{ - ecs_pool_t *pool = ®istry->comp_pools[component]; - const component_t *comp = ®istry->components[component]; - __auto_type destroy = comp->destroy; - if (destroy) { - byte *data = registry->comp_pools[component].data; - for (uint32_t i = 0; i < pool->count; i++) { - destroy (data + i * comp->size); - } - } - pool->count = 0; -} +#define IMPLEMENT_ECS_COMPONENT_Funcs +#include "QF/ecs.h" diff --git a/libs/ecs/ecs.c b/libs/ecs/ecs.c new file mode 100644 index 000000000..395fbdba1 --- /dev/null +++ b/libs/ecs/ecs.c @@ -0,0 +1,190 @@ +/* + ecs.c + + Entity Component System + + Copyright (C) 2022 Bill Currke + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "QF/heapsort.h" +#include "QF/sys.h" + +#define IMPLEMENT_ECS_Funcs +#include "QF/ecs.h" + +VISIBLE ecs_registry_t * +ECS_NewRegistry (void) +{ + ecs_registry_t *reg = calloc (1, sizeof (ecs_registry_t)); + reg->next = Ent_Index (nullent); + return reg; +} + +VISIBLE void +ECS_DelRegistry (ecs_registry_t *registry) +{ + free (registry->entities); + for (uint32_t i = 0; i < registry->num_components; i++) { + free (registry->comp_pools[i].sparse); + free (registry->comp_pools[i].dense); + free (registry->comp_pools[i].data); + } + free (registry->comp_pools); + free (registry); +} + +VISIBLE void +ECS_RegisterComponents (ecs_registry_t *registry, + const component_t *components, uint32_t count) +{ + registry->num_components = count; + registry->components = components; + registry->comp_pools = calloc (count, sizeof (ecs_pool_t)); + size_t size = registry->max_entities * sizeof (uint32_t); + for (uint32_t i = 0; i < registry->num_components; i++) { + registry->comp_pools[i].sparse = malloc (size); + memset (registry->comp_pools[i].sparse, nullent, size); + } +} + +typedef struct { + __compar_d_fn_t cmp; + void *arg; + ecs_pool_t *pool; + const component_t *comp; +} ecs_sort_t; + +static int +ecs_compare (const void *a, const void *b, void *arg) +{ + ecs_sort_t *sortctx = arg; + return sortctx->cmp (a, b, sortctx->arg); +} + +static void +swap_uint32 (uint32_t *a, uint32_t *b) +{ + uint32_t t = *a; + *a = *b; + *b = t; +} + +static void +ecs_swap (void *_a, void *_b, void *arg) +{ + ecs_sort_t *sortctx = arg; + size_t size = sortctx->comp->size; + ecs_pool_t *pool = sortctx->pool; + uint32_t *a = _a; + uint32_t *b = _b; + uint32_t a_ind = a - pool->dense; + uint32_t b_ind = b - pool->dense; + uint32_t a_ent_ind = Ent_Index (pool->dense[a_ind]); + uint32_t b_ent_ind = Ent_Index (pool->dense[b_ind]); + __auto_type a_data = (byte *) pool->data + a_ind * size; + __auto_type b_data = (byte *) pool->data + b_ind * size; + Component_SwapElements (sortctx->comp, a_data, b_data); + swap_uint32 (a, b); + swap_uint32 (&pool->sparse[a_ent_ind], &pool->sparse[b_ent_ind]); +} + +VISIBLE void +ECS_SortComponentPool (ecs_registry_t *registry, uint32_t component, + __compar_d_fn_t cmp, void *arg) +{ + if (component >= registry->num_components) { + Sys_Error ("ECS_SortComponentPool: invalid component: %u", component); + } + ecs_pool_t *pool = ®istry->comp_pools[component]; + if (!pool->count) { + return; + } + __auto_type comp = ®istry->components[component]; + ecs_sort_t sortctx = { .cmp = cmp, .arg = arg, .pool = pool, .comp = comp }; + heapsort_s (pool->dense, pool->count, sizeof (uint32_t), + ecs_compare, ecs_swap, &sortctx); +} + +VISIBLE uint32_t +ECS_NewEntity (ecs_registry_t *registry) +{ + uint32_t ent; + if (registry->available) { + registry->available--; + uint32_t next = registry->next; + ent = next | Ent_Generation (registry->entities[next]); + registry->next = Ent_Index (registry->entities[next]); + registry->entities[next] = ent; + } else { + if (registry->num_entities == Ent_Index (nullent)) { + Sys_Error ("ECS_NewEntity: out of entities"); + } + if (registry->num_entities == registry->max_entities) { + registry->max_entities += ENT_GROW; + size_t size = registry->max_entities * sizeof (uint32_t); + registry->entities = realloc (registry->entities, size); + for (uint32_t i = 0; i < registry->num_components; i++) { + uint32_t *sparse = registry->comp_pools[i].sparse; + sparse = realloc (sparse, size); + memset (sparse + registry->max_entities - ENT_GROW, nullent, + ENT_GROW * sizeof (uint32_t)); + registry->comp_pools[i].sparse = sparse; + } + } + ent = registry->num_entities++; + // ent starts out with generation 0 + registry->entities[ent] = ent; + } + return ent; +} + +VISIBLE void +ECS_DelEntity (ecs_registry_t *registry, uint32_t ent) +{ + uint32_t next = registry->next | Ent_NextGen (Ent_Generation (ent)); + uint32_t id = Ent_Index (ent); + registry->entities[id] = next; + registry->next = id; + registry->available++; + + for (uint32_t i = 0; i < registry->num_components; i++) { + Ent_RemoveComponent (ent, i, registry); + } +} + +VISIBLE void +ECS_RemoveEntities (ecs_registry_t *registry, uint32_t component) +{ + ecs_pool_t *pool = ®istry->comp_pools[component]; + const component_t *comp = ®istry->components[component]; + __auto_type destroy = comp->destroy; + if (destroy) { + byte *data = registry->comp_pools[component].data; + for (uint32_t i = 0; i < pool->count; i++) { + destroy (data + i * comp->size); + } + } + pool->count = 0; +} diff --git a/libs/ecs/entity.c b/libs/ecs/entity.c new file mode 100644 index 000000000..ef0441636 --- /dev/null +++ b/libs/ecs/entity.c @@ -0,0 +1,77 @@ +/* + entity.c + + ECS entity management + + Copyright (C) 2022 Bill Currke + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "QF/heapsort.h" +#include "QF/sys.h" + +#define IMPLEMENT_ECS_ENTITY_Funcs +#include "QF/ecs.h" + +VISIBLE void * +Ent_AddComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry) +{ + ecs_pool_t *pool = ®istry->comp_pools[comp]; + uint32_t id = Ent_Index (ent); + uint32_t ind = pool->sparse[id]; + if (ind >= pool->count || pool->dense[ind] != ent) { + if (pool->count == pool->max_count) { + pool->max_count += COMP_GROW; + pool->dense = realloc (pool->dense, + pool->max_count * sizeof (uint32_t)); + Component_ResizeArray (®istry->components[comp], &pool->data, + pool->max_count); + } + uint32_t ind = pool->count++; + pool->sparse[id] = ind; + pool->dense[ind] = ent; + } + return Ent_GetComponent (ent, comp, registry); +} + +VISIBLE void +Ent_RemoveComponent (uint32_t ent, uint32_t comp, ecs_registry_t *registry) +{ + uint32_t id = Ent_Index (ent); + ecs_pool_t *pool = ®istry->comp_pools[comp]; + uint32_t ind = pool->sparse[id]; + if (ind < pool->count && pool->dense[ind] == ent) { + uint32_t last = pool->count - 1; + Component_DestroyElements (®istry->components[comp], pool->data, + ind, 1); + if (last > ind) { + pool->sparse[Ent_Index (pool->dense[last])] = ind; + pool->dense[ind] = pool->dense[last]; + Component_MoveElements (®istry->components[comp], pool->data, + ind, last, 1); + } + pool->count--; + pool->sparse[id] = nullent; + } +} diff --git a/libs/ecs/hierarchy.c b/libs/ecs/hierarchy.c index 3418591c6..19b8079fc 100644 --- a/libs/ecs/hierarchy.c +++ b/libs/ecs/hierarchy.c @@ -1,7 +1,7 @@ /* hierarchy.c - General hierarchy handling + ECS hierarchy handling Copyright (C) 2021 Bill Currke @@ -37,8 +37,7 @@ #include "QF/sys.h" -#include "QF/ecs/component.h" -#include "QF/ecs/hierarchy.h" +#include "QF/ecs.h" static component_t ent_component = { .size = sizeof (uint32_t) }; static component_t childCount_component = { .size = sizeof (uint32_t) }; diff --git a/libs/ui/passage.c b/libs/ui/passage.c index d17d0bc0a..163f7de2b 100644 --- a/libs/ui/passage.c +++ b/libs/ui/passage.c @@ -39,7 +39,7 @@ #include "QF/qtypes.h" #include "QF/sys.h" -#include "QF/ecs/component.h" +#include "QF/ecs.h" #include "QF/ui/passage.h" #include "QF/ui/view.h"