Pico Headers
Loading...
Searching...
No Matches
pico_ecs.h
Go to the documentation of this file.
1
160#ifndef PICO_ECS_H
161#define PICO_ECS_H
162
163#include <stdbool.h> // bool, true, false
164#include <stddef.h> // size_t
165#include <stdint.h> // uint32_t
166#include <limits.h> // SIZE_MAX
167
168#ifdef __cplusplus
169extern "C" {
170#endif
171
175typedef struct ecs_s ecs_t;
176
180#ifndef ECS_ID_TYPE
181#define ECS_ID_TYPE uint64_t
182#endif
183
188
192#ifndef ECS_MASK_TYPE
193#define ECS_MASK_TYPE uint64_t
194#endif
195
200
204typedef int32_t ecs_ret_t;
205
210
214typedef struct ecs_comp_t { ecs_id_t id; } ecs_comp_t;
215
220
224#define ECS_INVALID_ID ((ecs_id_t)-1)
225
229#define ECS_IS_INVALID(obj) ((obj.id) == ECS_INVALID_ID)
230
239ecs_t* ecs_new(size_t entity_count, void* mem_ctx);
240
246void ecs_free(ecs_t* ecs);
247
251void ecs_reset(ecs_t* ecs);
252
260typedef void (*ecs_constructor_fn)(ecs_t* ecs,
261 ecs_entity_t entity,
262 void* comp_ptr,
263 void* args);
264
272typedef void (*ecs_destructor_fn)(ecs_t* ecs,
273 ecs_entity_t entity,
274 void* comp_ptr);
275
290 size_t size,
291 ecs_constructor_fn constructor,
292 ecs_destructor_fn destructor);
293
306 ecs_entity_t* entities,
307 size_t entity_count,
308 void* udata);
309
317typedef void (*ecs_added_fn)(ecs_t* ecs, ecs_entity_t entity, void* udata);
318
326typedef void (*ecs_removed_fn)(ecs_t* ecs, ecs_entity_t entity, void* udata);
327
344 ecs_mask_t mask,
345 ecs_system_fn system_cb,
346 ecs_added_fn add_cb,
347 ecs_removed_fn remove_cb,
348 void* udata);
358
368
376
384
395 ecs_system_t sys,
396 ecs_system_fn system_cb,
397 ecs_added_fn add_cb,
398 ecs_removed_fn remove_cb);
399
407void ecs_set_system_udata(ecs_t* ecs, ecs_system_t sys, void* udata);
408
417
426
435
440
449
457bool ecs_is_ready(ecs_t* ecs, ecs_entity_t entity);
458
468bool ecs_has(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp);
469
479void* ecs_add(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp, void* args);
480
490void* ecs_get(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp);
491
500void ecs_destroy(ecs_t* ecs, ecs_entity_t entity);
501
509void ecs_remove(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp);
510
521
533
534#ifdef __cplusplus
535}
536#endif
537
538#endif // PICO_ECS_H
539
540#ifdef PICO_ECS_IMPLEMENTATION // Define once
541
542#ifndef PICO_ECS_MAX_COMPONENTS
543#define PICO_ECS_MAX_COMPONENTS 32
544#endif
545
546#ifndef PICO_ECS_MAX_SYSTEMS
547#define PICO_ECS_MAX_SYSTEMS 16
548#endif
549
550#ifdef NDEBUG
551 #define PICO_ECS_ASSERT(expr) ((void)0)
552#else
553 #ifndef PICO_ECS_ASSERT
554 #include <assert.h>
555 #define PICO_ECS_ASSERT(expr) (assert(expr))
556 #endif
557#endif
558
559#if !defined(PICO_ECS_MALLOC) || !defined(PICO_ECS_REALLOC) || !defined(PICO_ECS_FREE)
560#include <stdlib.h>
561#define PICO_ECS_MALLOC(size, ctx) (malloc(size))
562#define PICO_ECS_REALLOC(ptr, size, ctx) (realloc(ptr, size))
563#define PICO_ECS_FREE(ptr, ctx) (free(ptr))
564#endif
565
566#ifndef PICO_ECS_MEMSET
567 #include <string.h>
568 #define PICO_ECS_MEMSET memset
569#endif
570
571/*=============================================================================
572 * Aliases
573 *============================================================================*/
574
575#define ECS_ASSERT PICO_ECS_ASSERT
576#define ECS_MAX_COMPONENTS PICO_ECS_MAX_COMPONENTS
577#define ECS_MAX_SYSTEMS PICO_ECS_MAX_SYSTEMS
578#define ECS_MALLOC PICO_ECS_MALLOC
579#define ECS_REALLOC PICO_ECS_REALLOC
580#define ECS_FREE PICO_ECS_FREE
581#define ECS_MEMSET PICO_ECS_MEMSET
582
583/*=============================================================================
584 * Data structures
585 *============================================================================*/
586
587#if ECS_MAX_COMPONENTS <= 32
588typedef uint32_t ecs_bitset_t;
589#elif ECS_MAX_COMPONENTS <= 64
590typedef uint64_t ecs_bitset_t;
591#else
592#define ECS_BITSET_WIDTH 64
593#define ECS_BITSET_SIZE (((ECS_MAX_COMPONENTS - 1) / ECS_BITSET_WIDTH) + 1)
594
595typedef struct
596{
597 uint64_t array[ECS_BITSET_SIZE];
598} ecs_bitset_t;
599
600#endif // ECS_MAX_COMPONENTS
601
602// Data-structure for a packed array implementation that provides O(1) functions
603// for adding, removing, and accessing entity IDs
604typedef struct
605{
606 size_t capacity;
607 size_t size;
608 size_t* sparse;
609 ecs_entity_t* dense;
610} ecs_sparse_set_t;
611
612// A data-structure for providing O(1) operations for working with IDs
613typedef struct
614{
615 size_t capacity;
616 size_t size; // array size
617 ecs_id_t* data;
618} ecs_id_array_t;
619
620typedef struct
621{
622 size_t capacity;
623 size_t size; // component size
624 void* data;
625} ecs_comp_array_t;
626
627typedef struct
628{
629 ecs_bitset_t comp_bits;
630 bool active;
631 bool ready;
632} ecs_entity_data_t;
633
634typedef struct
635{
636 ecs_constructor_fn constructor;
637 ecs_destructor_fn destructor;
638} ecs_comp_data_t;
639
640typedef struct
641{
642 bool active;
643 ecs_sparse_set_t entity_ids;
644 ecs_mask_t mask;
645 ecs_system_fn system_cb;
646 ecs_added_fn add_cb;
647 ecs_removed_fn remove_cb;
648 ecs_bitset_t require_bits;
649 ecs_bitset_t exclude_bits;
650 void* udata;
651} ecs_sys_data_t;
652
653struct ecs_s
654{
655 ecs_id_array_t entity_pool;
656 ecs_id_array_t add_queue;
657 ecs_id_array_t remove_queue;
658 ecs_id_array_t destroy_queue;
659 ecs_entity_data_t* entities;
660 size_t entity_count;
661 size_t next_entity_id;
662 ecs_comp_data_t comps[ECS_MAX_COMPONENTS];
663 ecs_comp_array_t comp_arrays[ECS_MAX_COMPONENTS];
664 size_t comp_count;
665 ecs_sys_data_t systems[ECS_MAX_SYSTEMS];
666 size_t system_count;
667 int active_system;
668 void* mem_ctx;
669};
670
671/*=============================================================================
672 * Handle constructors
673 *============================================================================*/
674static inline ecs_entity_t ecs_make_entity(ecs_id_t id);
675static inline ecs_comp_t ecs_make_comp(ecs_id_t id);
676static inline ecs_system_t ecs_make_system(ecs_id_t id);
677
678/*=============================================================================
679 * Realloc wrapper
680 *============================================================================*/
681static void* ecs_realloc_zero(ecs_t* ecs, void* ptr, size_t old_size, size_t new_size);
682
683/*=============================================================================
684 * Calls destructors on all components of the entity
685 *============================================================================*/
686static void ecs_destruct(ecs_t* ecs, ecs_id_t entity);
687
688/*=============================================================================
689 * Tests if entity is active (created)
690 *============================================================================*/
691static inline bool ecs_is_active(ecs_t* ecs, ecs_id_t entity_id);
692
693/*=============================================================================
694 * Functions to flush destroyed entities and removed component
695 *============================================================================*/
696static void ecs_flush_added(ecs_t* ecs, ecs_id_t sys_id);
697static void ecs_flush_removed(ecs_t* ecs, ecs_id_t sys_id);
698static void ecs_flush_destroyed(ecs_t* ecs, ecs_id_t sys_id);
699
700/*=============================================================================
701 * Bitset functions
702 *============================================================================*/
703static inline void ecs_bitset_flip(ecs_bitset_t* set, int bit, bool on);
704static inline bool ecs_bitset_is_zero(ecs_bitset_t* set);
705static inline bool ecs_bitset_test(ecs_bitset_t* set, int bit);
706static inline ecs_bitset_t ecs_bitset_and(ecs_bitset_t* set1, ecs_bitset_t* set2);
707static inline ecs_bitset_t ecs_bitset_or(ecs_bitset_t* set1, ecs_bitset_t* set2);
708static inline ecs_bitset_t ecs_bitset_not(ecs_bitset_t* set);
709static inline bool ecs_bitset_equal(ecs_bitset_t* set1, ecs_bitset_t* set2);
710static inline bool ecs_bitset_true(ecs_bitset_t* set);
711
712/*=============================================================================
713 * Sparse set functions
714 *============================================================================*/
715static void ecs_sparse_set_init(ecs_t* ecs, ecs_sparse_set_t* set, size_t capacity);
716static void ecs_sparse_set_free(ecs_t* ecs, ecs_sparse_set_t* set);
717static bool ecs_sparse_set_add(ecs_t* ecs, ecs_sparse_set_t* set, ecs_id_t id);
718static inline bool ecs_sparse_set_find(ecs_sparse_set_t* set, ecs_id_t id, size_t* found);
719static inline bool ecs_sparse_set_remove(ecs_sparse_set_t* set, ecs_id_t id);
720
721/*=============================================================================
722 * System entity add/remove functions
723 *============================================================================*/
724static bool ecs_entity_system_test(ecs_bitset_t* require_bits,
725 ecs_bitset_t* exclude_bits,
726 ecs_bitset_t* entity_bits);
727
728static bool ecs_entity_system_remove_test(ecs_bitset_t* require_bits,
729 ecs_bitset_t* exclude_bits,
730 ecs_bitset_t* entity_bits);
731
732/*=============================================================================
733 * ID array functions
734 *============================================================================*/
735static void ecs_id_array_init(ecs_t* ecs, ecs_id_array_t* pool, size_t capacity);
736static void ecs_id_array_free(ecs_t* ecs, ecs_id_array_t* pool);
737static inline void ecs_id_array_push(ecs_t* ecs, ecs_id_array_t* pool, ecs_id_t id);
738static inline ecs_id_t ecs_id_array_pop(ecs_id_array_t* pool);
739static int ecs_id_array_size(ecs_id_array_t* pool);
740
741/*=============================================================================
742 * Component array functions
743 *============================================================================*/
744static void ecs_comp_array_init(ecs_t* ecs, ecs_comp_array_t* array, size_t size, size_t capacity);
745static void ecs_comp_array_free(ecs_t* ecs, ecs_comp_array_t* array);
746static void ecs_comp_array_resize(ecs_t* ecs, ecs_comp_array_t* array, ecs_id_t id);
747
748/*=============================================================================
749 * Validation functions
750 *============================================================================*/
751#ifndef NDEBUG
752static bool ecs_is_not_null(void* ptr);
753static bool ecs_is_valid_component_id(ecs_id_t id);
754static bool ecs_is_valid_system_id(ecs_id_t id);
755static bool ecs_is_valid_id(ecs_id_t id);
756static bool ecs_is_valid_capacity(size_t capacity, size_t elem_size);
757static bool ecs_is_entity_ready(ecs_t* ecs, ecs_id_t entity_id);
758static bool ecs_is_component_ready(ecs_t* ecs, ecs_id_t comp_id);
759static bool ecs_is_system_ready(ecs_t* ecs, ecs_id_t sys_id);
760#endif // NDEBUG
761
762/*=============================================================================
763 * Public API implementation
764 *============================================================================*/
765
766ecs_t* ecs_new(size_t entity_count, void* mem_ctx)
767{
768 ECS_ASSERT(entity_count > 0);
769 ECS_ASSERT(!ecs_is_valid_id(ECS_INVALID_ID) && "ecs_id_t is signed");
770
771 ecs_t* ecs = (ecs_t*)ECS_MALLOC(sizeof(ecs_t), mem_ctx);
772
773 // Out of memory
774 if (NULL == ecs)
775 return NULL;
776
777 ECS_MEMSET(ecs, 0, sizeof(ecs_t));
778
779 ecs->entity_count = (entity_count > 0) ? entity_count : 1;
780 ecs->next_entity_id = 0;
781 ecs->active_system = -1;
782 ecs->mem_ctx = mem_ctx;
783
784 // Initialize entity pool and queues
785 ecs_id_array_init(ecs, &ecs->entity_pool, entity_count);
786 ecs_id_array_init(ecs, &ecs->add_queue, entity_count);
787 ecs_id_array_init(ecs, &ecs->remove_queue, entity_count);
788 ecs_id_array_init(ecs, &ecs->destroy_queue, entity_count);
789
790 // Allocate entity array
791 ECS_ASSERT(ecs_is_valid_capacity(ecs->entity_count, sizeof(ecs_entity_data_t)));
792 ecs->entities = (ecs_entity_data_t*)ECS_MALLOC(ecs->entity_count * sizeof(ecs_entity_data_t),
793 ecs->mem_ctx);
794
795 // Zero entity array
796 ECS_MEMSET(ecs->entities, 0, ecs->entity_count * sizeof(ecs_entity_data_t));
797
798 return ecs;
799}
800
801void ecs_free(ecs_t* ecs)
802{
803 ECS_ASSERT(ecs_is_not_null(ecs));
804
805 for (ecs_id_t entity_id = 0; entity_id < ecs->entity_count; entity_id++)
806 {
807 if (ecs->entities[entity_id].active)
808 ecs_destruct(ecs, entity_id);
809 }
810
811 ecs_id_array_free(ecs, &ecs->entity_pool);
812 ecs_id_array_free(ecs, &ecs->add_queue);
813 ecs_id_array_free(ecs, &ecs->remove_queue);
814 ecs_id_array_free(ecs, &ecs->destroy_queue);
815
816 for (ecs_id_t comp_id = 0; comp_id < ecs->comp_count; comp_id++)
817 {
818 ecs_comp_array_t* comp_array = &ecs->comp_arrays[comp_id];
819 ecs_comp_array_free(ecs, comp_array);
820 }
821
822 for (ecs_id_t sys_id = 0; sys_id < ecs->system_count; sys_id++)
823 {
824 ecs_sys_data_t* sys = &ecs->systems[sys_id];
825 ecs_sparse_set_free(ecs, &sys->entity_ids);
826 }
827
828 ECS_FREE(ecs->entities, ecs->mem_ctx);
829 ECS_FREE(ecs, ecs->mem_ctx);
830}
831
832void ecs_reset(ecs_t* ecs)
833{
834 ECS_ASSERT(ecs_is_not_null(ecs));
835
836 for (ecs_id_t entity_id = 0; entity_id < ecs->entity_count; entity_id++)
837 {
838 if (ecs->entities[entity_id].active)
839 ecs_destruct(ecs, entity_id);
840 }
841
842 ecs->entity_pool.size = 0;
843 ecs->add_queue.size = 0;
844 ecs->destroy_queue.size = 0;
845 ecs->remove_queue.size = 0;
846
847 ECS_MEMSET(ecs->entities, 0, ecs->entity_count * sizeof(ecs_entity_data_t));
848
849 ecs->next_entity_id = 0;
850
851 for (ecs_id_t sys_id = 0; sys_id < ecs->system_count; sys_id++)
852 {
853 ecs->systems[sys_id].entity_ids.size = 0;
854 }
855}
856
858 size_t size,
859 ecs_constructor_fn constructor,
860 ecs_destructor_fn destructor)
861{
862 ECS_ASSERT(ecs_is_not_null(ecs));
863 ECS_ASSERT(ecs->comp_count < ECS_MAX_COMPONENTS);
864 ECS_ASSERT(size > 0);
865
866 ecs_comp_t comp = ecs_make_comp(ecs->comp_count);
867
868 ecs_comp_array_t* comp_array = &ecs->comp_arrays[comp.id];
869 ecs_comp_array_init(ecs, comp_array, size, ecs->entity_count);
870
871 ecs->comps[comp.id].constructor = constructor;
872 ecs->comps[comp.id].destructor = destructor;
873
874 ecs->comp_count++;
875
876 return comp;
877}
878
880 ecs_mask_t mask,
881 ecs_system_fn system_cb,
882 ecs_added_fn add_cb,
883 ecs_removed_fn remove_cb,
884 void* udata)
885{
886 ECS_ASSERT(ecs_is_not_null(ecs));
887 ECS_ASSERT(ecs->system_count < ECS_MAX_SYSTEMS);
888 ECS_ASSERT(NULL != system_cb);
889
890 ecs_system_t sys = ecs_make_system(ecs->system_count);
891 ecs_sys_data_t* sys_data = &ecs->systems[sys.id];
892
893 ecs_sparse_set_init(ecs, &sys_data->entity_ids, ecs->entity_count);
894
895 sys_data->active = true;
896 sys_data->mask = mask;
897 sys_data->system_cb = system_cb;
898 sys_data->add_cb = add_cb;
899 sys_data->remove_cb = remove_cb;
900 sys_data->udata = udata;
901
902 ecs->system_count++;
903
904 return sys;
905}
906
908{
909 ECS_ASSERT(ecs_is_not_null(ecs));
910 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
911 ECS_ASSERT(ecs_is_valid_component_id(comp.id));
912 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
913 ECS_ASSERT(ecs_is_component_ready(ecs, comp.id));
914
915 // Set system component bit for the specified component
916 ecs_sys_data_t* sys_data = &ecs->systems[sys.id];
917 ecs_bitset_flip(&sys_data->require_bits, comp.id, true);
918}
919
921{
922 ECS_ASSERT(ecs_is_not_null(ecs));
923 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
924 ECS_ASSERT(ecs_is_valid_component_id(comp.id));
925 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
926 ECS_ASSERT(ecs_is_component_ready(ecs, comp.id));
927
928 // Set system component bit for the specified component
929 ecs_sys_data_t* sys_data = &ecs->systems[sys.id];
930 ecs_bitset_flip(&sys_data->exclude_bits, comp.id, true);
931}
932
934{
935 ECS_ASSERT(ecs_is_not_null(ecs));
936 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
937 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
938
939 ecs_sys_data_t* sys_data = &ecs->systems[sys.id];
940 sys_data->active = true;
941}
942
944{
945 ECS_ASSERT(ecs_is_not_null(ecs));
946 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
947 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
948
949 ecs_sys_data_t* sys_data = &ecs->systems[sys.id];
950 sys_data->active = false;
951}
952
954 ecs_system_t sys,
955 ecs_system_fn system_cb,
956 ecs_added_fn add_cb,
957 ecs_removed_fn remove_cb)
958{
959 ECS_ASSERT(ecs_is_not_null(ecs));
960 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
961 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
962 ECS_ASSERT(NULL != system_cb);
963
964 ecs_sys_data_t* sys_data = &ecs->systems[sys.id];
965 sys_data->system_cb = system_cb;
966 sys_data->add_cb = add_cb;
967 sys_data->remove_cb = remove_cb;
968}
969
970void ecs_set_system_udata(ecs_t* ecs, ecs_system_t sys, void* udata)
971{
972 ECS_ASSERT(ecs_is_not_null(ecs));
973 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
974 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
975
976 ecs->systems[sys.id].udata = udata;
977}
978
980{
981 ECS_ASSERT(ecs_is_not_null(ecs));
982 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
983 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
984
985 return ecs->systems[sys.id].udata;
986}
987
989{
990 ECS_ASSERT(ecs_is_not_null(ecs));
991 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
992 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
993
994 ecs->systems[sys.id].mask = mask;
995}
996
998{
999 ECS_ASSERT(ecs_is_not_null(ecs));
1000 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
1001 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
1002
1003 return ecs->systems[sys.id].mask;
1004}
1005
1007{
1008 return ecs->systems[sys.id].entity_ids.size;
1009}
1010
1012{
1013 ECS_ASSERT(ecs_is_not_null(ecs));
1014
1015 ecs_id_t entity_id = 0;
1016
1017 // If there is an ID in the pool, pop it
1018 ecs_id_array_t* pool = &ecs->entity_pool;
1019
1020 if (0 != ecs_id_array_size(pool))
1021 {
1022 entity_id = ecs_id_array_pop(pool);
1023 }
1024 else
1025 {
1026 // Otherwise, issue a fresh ID
1027 entity_id = ecs->next_entity_id++;
1028
1029 // Grow the entities array if necessary
1030 if (entity_id >= ecs->entity_count)
1031 {
1032 size_t old_count = ecs->entity_count;
1033 size_t new_count = 2 * old_count;
1034
1035 ECS_ASSERT(ecs_is_valid_capacity(new_count, sizeof(ecs_entity_data_t)));
1036 ecs->entities = (ecs_entity_data_t*)ecs_realloc_zero(ecs, ecs->entities,
1037 old_count * sizeof(ecs_entity_data_t),
1038 new_count * sizeof(ecs_entity_data_t));
1039
1040 ecs->entity_count = new_count;
1041 }
1042 }
1043
1044 // Activate the entity and return a handle
1045 ecs->entities[entity_id].active = true;
1046 ecs->entities[entity_id].ready = true;
1047
1048 return ecs_make_entity(entity_id);
1049}
1050
1051bool ecs_is_ready(ecs_t* ecs, ecs_entity_t entity)
1052{
1053 ECS_ASSERT(ecs_is_not_null(ecs));
1054
1055 return ecs->entities[entity.id].ready;
1056}
1057
1058void ecs_destroy(ecs_t* ecs, ecs_entity_t entity)
1059{
1060 ECS_ASSERT(ecs_is_not_null(ecs));
1061 ECS_ASSERT(ecs_is_valid_id(entity.id));
1062 ECS_ASSERT(ecs_is_active(ecs, entity.id));
1063
1064 // Load entity data
1065 ecs_entity_data_t* entity_data = &ecs->entities[entity.id];
1066
1067 // Remove entity from systems
1068 for (ecs_id_t sys_id = 0; sys_id < ecs->system_count; sys_id++)
1069 {
1070 // Skip if the system is active and matches the current system ID. This
1071 // system will be processed below
1072 if (ecs->active_system == (int)sys_id)
1073 continue;
1074
1075 ecs_sys_data_t* sys_data = &ecs->systems[sys_id];
1076
1077 // Test to see if entity's components matches the system
1078 if (ecs_entity_system_test(&sys_data->require_bits,
1079 &sys_data->exclude_bits,
1080 &entity_data->comp_bits))
1081 {
1082 // Directly remove from sparse set since no system is being
1083 // processed
1084 if (ecs_sparse_set_remove(&sys_data->entity_ids, entity.id))
1085 {
1086 if (sys_data->remove_cb)
1087 sys_data->remove_cb(ecs, entity, sys_data->udata);
1088 }
1089 }
1090 }
1091
1092 // True if a system is being processed
1093 if (ecs->active_system >= 0)
1094 {
1095 ecs_sys_data_t* sys_data = &ecs->systems[ecs->active_system];
1096
1097 // Test to see if entity's components matches the system
1098 if (ecs_entity_system_test(&sys_data->require_bits,
1099 &sys_data->exclude_bits,
1100 &entity_data->comp_bits))
1101 {
1102 // Since a system is being processed we need to protect its sparse
1103 // set, so we queue a removed command
1104 if (ecs_sparse_set_find(&sys_data->entity_ids, entity.id, NULL))
1105 {
1106 if (sys_data->remove_cb)
1107 sys_data->remove_cb(ecs, entity, sys_data->udata);
1108
1109 ecs_id_array_push(ecs, &ecs->destroy_queue, entity.id);
1110
1111 entity_data->ready = false;
1112 entity_data->active = true;
1113 }
1114 }
1115 }
1116
1117 // Call destructors on entity components
1118 ecs_destruct(ecs, entity.id);
1119
1120 // If a system is not active, then clean up
1121 if (ecs->active_system < 0)
1122 {
1123 // Push entity ID into pool
1124 ecs_id_array_t* pool = &ecs->entity_pool;
1125 ecs_id_array_push(ecs, pool, entity.id);
1126
1127 // Reset entity (sets bitset to 0 and, active and ready to false)
1128 ECS_MEMSET(entity_data, 0, sizeof(ecs_entity_data_t));
1129 }
1130}
1131
1132bool ecs_has(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp)
1133{
1134 ECS_ASSERT(ecs_is_not_null(ecs));
1135 ECS_ASSERT(ecs_is_valid_id(entity.id));
1136 ECS_ASSERT(ecs_is_valid_component_id(comp.id));
1137 ECS_ASSERT(ecs_is_entity_ready(ecs, entity.id));
1138
1139 // Load entity data
1140 ecs_entity_data_t* entity_data = &ecs->entities[entity.id];
1141
1142 // Return true if the component belongs to the entity
1143 return ecs_bitset_test(&entity_data->comp_bits, comp.id);
1144}
1145
1146void* ecs_get(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp)
1147{
1148 ECS_ASSERT(ecs_is_not_null(ecs));
1149 ECS_ASSERT(ecs_is_valid_id(entity.id));
1150 ECS_ASSERT(ecs_is_valid_component_id(comp.id));
1151 ECS_ASSERT(ecs_is_component_ready(ecs, comp.id));
1152 ECS_ASSERT(ecs_is_entity_ready(ecs, entity.id));
1153
1154 // Return pointer to component
1155 // eid0, eid1 eid2, ...
1156 // [comp0, comp1, comp2, ...]
1157 ecs_comp_array_t* comp_array = &ecs->comp_arrays[comp.id];
1158 return (char*)comp_array->data + (comp_array->size * entity.id);
1159}
1160
1161void* ecs_add(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp, void* args)
1162{
1163 ECS_ASSERT(ecs_is_not_null(ecs));
1164 ECS_ASSERT(ecs_is_valid_id(entity.id));
1165 ECS_ASSERT(ecs_is_valid_component_id(comp.id));
1166 ECS_ASSERT(ecs_is_entity_ready(ecs, entity.id));
1167 ECS_ASSERT(ecs_is_component_ready(ecs, comp.id));
1168
1169 // Load entity data
1170 ecs_entity_data_t* entity_data = &ecs->entities[entity.id];
1171
1172 // Load component
1173 ecs_comp_array_t* comp_array = &ecs->comp_arrays[comp.id];
1174 ecs_comp_data_t* comp_data = &ecs->comps[comp.id];
1175
1176 // Grow the component array
1177 ecs_comp_array_resize(ecs, comp_array, entity.id);
1178
1179 // Get pointer to component
1180 void* comp_ptr = ecs_get(ecs, entity, comp);
1181
1182 // Zero component
1183 ECS_MEMSET(comp_ptr, 0, comp_array->size);
1184
1185 // Call constructor
1186 if (comp_data->constructor)
1187 comp_data->constructor(ecs, entity, comp_ptr, args);
1188
1189 // Set entity component bit that determines which systems this entity
1190 // belongs to
1191 ecs_bitset_flip(&entity_data->comp_bits, comp.id, true);
1192
1193 // Add or remove entity from systems
1194 for (ecs_id_t sys_id = 0; sys_id < ecs->system_count; sys_id++)
1195 {
1196 // Skip if a system is active and matches the current system ID, this
1197 // system will be processed below
1198 if (ecs->active_system == (int)sys_id)
1199 continue;
1200
1201 ecs_sys_data_t* sys_data = &ecs->systems[sys_id];
1202
1203 // Test to see if entity's components matches the system
1204 if (ecs_entity_system_test(&sys_data->require_bits,
1205 &sys_data->exclude_bits,
1206 &entity_data->comp_bits))
1207 {
1208 // Add the entity directly to the sparse set
1209 if (ecs_sparse_set_add(ecs, &sys_data->entity_ids, entity.id))
1210 {
1211 if (sys_data->add_cb)
1212 sys_data->add_cb(ecs, entity, sys_data->udata);
1213 }
1214 }
1215 else
1216 {
1217 // As a minor optimization, check if the system excludes components
1218 if (!ecs_bitset_is_zero(&sys_data->exclude_bits))
1219 {
1220 // Just remove the entity from the sparse set if its components
1221 // no longer match
1222 if (ecs_sparse_set_remove(&sys_data->entity_ids, entity.id))
1223 {
1224 if (sys_data->remove_cb)
1225 sys_data->remove_cb(ecs, entity, sys_data->udata);
1226 }
1227 }
1228 }
1229 }
1230
1231 // True if a system is running
1232 if (ecs->active_system >= 0)
1233 {
1234 // Load active system data
1235 ecs_sys_data_t* sys_data = &ecs->systems[ecs->active_system];
1236
1237 // Test to see if entity's components matches the system
1238 if (ecs_entity_system_test(&sys_data->require_bits,
1239 &sys_data->exclude_bits,
1240 &entity_data->comp_bits))
1241 {
1242 ecs_sparse_set_t* set = &sys_data->entity_ids;
1243
1244 // If the sparse set has room to grow without allocation,
1245 // directly add the entity
1246 if (set->size < set->capacity)
1247 {
1248 // Add the entity directly to the sparse set
1249 if (ecs_sparse_set_add(ecs, set, entity.id))
1250 {
1251 if (sys_data->add_cb)
1252 sys_data->add_cb(ecs, entity, sys_data->udata);
1253 }
1254 }
1255 else
1256 {
1257 // The sparse set is full, so queue an add command
1258 if (!ecs_sparse_set_find(set, entity.id, NULL))
1259 {
1260 if (sys_data->add_cb)
1261 sys_data->add_cb(ecs, entity, sys_data->udata);
1262
1263 ecs_id_array_push(ecs, &ecs->add_queue, entity.id);
1264 }
1265 }
1266 }
1267 else
1268 {
1269 // As a minor optimization, check if the system excludes components
1270 if (!ecs_bitset_is_zero(&sys_data->exclude_bits))
1271 {
1272 // Since we are updating the active system, we need to protect
1273 // the sparse set. Thus, if the entity is in the set, queue
1274 // a remove command
1275 if (ecs_sparse_set_find(&sys_data->entity_ids, entity.id, NULL))
1276 {
1277 if (sys_data->remove_cb)
1278 sys_data->remove_cb(ecs, entity, sys_data->udata);
1279
1280 ecs_id_array_push(ecs, &ecs->remove_queue, entity.id);
1281 }
1282 }
1283 }
1284 }
1285
1286 // Return component
1287 return comp_ptr;
1288}
1289
1290void ecs_remove(ecs_t* ecs, ecs_entity_t entity, ecs_comp_t comp)
1291{
1292 ECS_ASSERT(ecs_is_not_null(ecs));
1293 ECS_ASSERT(ecs_is_valid_id(entity.id));
1294 ECS_ASSERT(ecs_is_valid_component_id(comp.id));
1295 ECS_ASSERT(ecs_is_component_ready(ecs, comp.id));
1296 ECS_ASSERT(ecs_is_entity_ready(ecs, entity.id));
1297
1298 // Load entity data
1299 ecs_entity_data_t* entity_data = &ecs->entities[entity.id];
1300
1301 // Create bit mask with comp bit flipped on
1302 ecs_bitset_t comp_bit;
1303
1304 ECS_MEMSET(&comp_bit, 0, sizeof(ecs_bitset_t));
1305 ecs_bitset_flip(&comp_bit, comp.id, true);
1306
1307 // Add or remove entity from systems
1308 for (ecs_id_t sys_id = 0; sys_id < ecs->system_count; sys_id++)
1309 {
1310 // Skip if the system is active and matches the current system ID. This
1311 // system will be processed below
1312 if (ecs->active_system == (int)sys_id)
1313 continue;
1314
1315 ecs_sys_data_t* sys_data = &ecs->systems[sys_id];
1316
1317 // Test to see if entity's components matches the system
1318 if (ecs_entity_system_remove_test(&sys_data->require_bits,
1319 &sys_data->exclude_bits,
1320 &comp_bit))
1321 {
1322 // No system is running, so we can directly remove the entity
1323 if (ecs_sparse_set_remove(&sys_data->entity_ids, entity.id))
1324 {
1325 if (sys_data->remove_cb)
1326 sys_data->remove_cb(ecs, entity, sys_data->udata);
1327 }
1328 }
1329 else
1330 {
1331 // As a minor optimization, check if the system excludes components
1332 if (!ecs_bitset_is_zero(&sys_data->exclude_bits))
1333 {
1334 // No system is running, so we can add the entity directly
1335 if (ecs_sparse_set_add(ecs, &sys_data->entity_ids, entity.id))
1336 {
1337 if (sys_data->add_cb)
1338 sys_data->add_cb(ecs, entity, sys_data->udata);
1339 }
1340 }
1341 }
1342 }
1343
1344 // True if a system is running
1345 if (ecs->active_system >= 0)
1346 {
1347 // Load active system data
1348 ecs_sys_data_t* sys_data = &ecs->systems[ecs->active_system];
1349
1350 // Test to see if entity's components matches the system
1351 if (ecs_entity_system_remove_test(&sys_data->require_bits,
1352 &sys_data->exclude_bits,
1353 &comp_bit))
1354 {
1355 // Since we are updating the active system, we need to protect
1356 // the sparse set. Thus, if the entity is in the set, queue
1357 // a remove command
1358
1359 if (ecs_sparse_set_find(&sys_data->entity_ids, entity.id, NULL))
1360 {
1361 if (sys_data->remove_cb)
1362 sys_data->remove_cb(ecs, entity, sys_data->udata);
1363
1364 ecs_id_array_push(ecs, &ecs->remove_queue, entity.id);
1365 }
1366 }
1367 else
1368 {
1369 // As a minor optimization, check if the system excludes components
1370 if (!ecs_bitset_is_zero(&sys_data->exclude_bits))
1371 {
1372 ecs_sparse_set_t* set = &sys_data->entity_ids;
1373
1374 // If the sparse set has room to grow without allocation,
1375 // directly add the entity
1376 if (set->size < set->capacity)
1377 {
1378 if (ecs_sparse_set_add(ecs, set, entity.id))
1379 {
1380 if (sys_data->add_cb)
1381 sys_data->add_cb(ecs, entity, sys_data->udata);
1382 }
1383 }
1384 else
1385 {
1386 // Sparse set is full, so queue an entity id add command
1387 if (!ecs_sparse_set_find(set, entity.id, NULL))
1388 {
1389 if (sys_data->add_cb)
1390 sys_data->add_cb(ecs, entity, sys_data->udata);
1391
1392 ecs_id_array_push(ecs, &ecs->add_queue, entity.id);
1393 }
1394 }
1395 }
1396 }
1397 }
1398
1399 // Destroy component
1400 ecs_comp_data_t* comp_data = &ecs->comps[comp.id];
1401
1402 if (comp_data->destructor)
1403 {
1404 void* comp_ptr = ecs_get(ecs, entity, comp);
1405 comp_data->destructor(ecs, entity, comp_ptr);
1406 }
1407
1408 // Reset the relevant component mask bit
1409 ecs_bitset_flip(&entity_data->comp_bits, comp.id, false);
1410}
1411
1413{
1414 ECS_ASSERT(ecs_is_not_null(ecs));
1415 ECS_ASSERT(ecs_is_valid_system_id(sys.id));
1416 ECS_ASSERT(ecs_is_system_ready(ecs, sys.id));
1417
1418 ecs_sys_data_t* sys_data = &ecs->systems[sys.id];
1419
1420 if (!sys_data->active)
1421 return 0;
1422
1423 if (0 != sys_data->mask && !(sys_data->mask & mask))
1424 return 0;
1425
1426 ecs->active_system = (int)sys.id;
1427
1428 ecs_ret_t code = sys_data->system_cb(ecs,
1429 sys_data->entity_ids.dense,
1430 sys_data->entity_ids.size,
1431 sys_data->udata);
1432
1433 ecs_flush_added(ecs, sys.id);
1434 ecs_flush_removed(ecs, sys.id);
1435 ecs_flush_destroyed(ecs, sys.id);
1436
1437 ecs->active_system = -1;
1438
1439 return code;
1440}
1441
1443{
1444 ECS_ASSERT(ecs_is_not_null(ecs));
1445
1446 for (ecs_id_t sys_id = 0; sys_id < ecs->system_count; sys_id++)
1447 {
1448 ecs_system_t sys = ecs_make_system(sys_id);
1449 ecs_ret_t code = ecs_run_system(ecs, sys, mask);
1450
1451 if (0 != code)
1452 return code;
1453 }
1454
1455 return 0;
1456}
1457
1458/*=============================================================================
1459 * Handle constructors
1460 *============================================================================*/
1461static inline ecs_entity_t ecs_make_entity(ecs_id_t id)
1462{
1463 ecs_entity_t entity = { id };
1464 return entity;
1465}
1466
1467static inline ecs_comp_t ecs_make_comp(ecs_id_t id)
1468{
1469 ecs_comp_t comp = { id };
1470 return comp;
1471}
1472
1473static inline ecs_system_t ecs_make_system(ecs_id_t id)
1474{
1475 ecs_system_t sys = { id };
1476 return sys;
1477}
1478
1479/*=============================================================================
1480 * Realloc wrapper
1481 *============================================================================*/
1482static void* ecs_realloc_zero(ecs_t* ecs, void* ptr, size_t old_size, size_t new_size)
1483{
1484 (void)ecs;
1485
1486 ptr = ECS_REALLOC(ptr, new_size, ecs->mem_ctx);
1487
1488 if (new_size > old_size && ptr) {
1489 size_t diff = new_size - old_size;
1490 void* start = ((char*)ptr)+ old_size;
1491 ECS_MEMSET(start, 0, diff);
1492 }
1493
1494 return ptr;
1495}
1496
1497/*=============================================================================
1498 * Tests if entity is active (created)
1499 *============================================================================*/
1500static inline bool ecs_is_active(ecs_t* ecs, ecs_id_t entity_id)
1501{
1502 ECS_ASSERT(ecs_is_not_null(ecs));
1503 return ecs->entities[entity_id].active;
1504}
1505
1506/*=============================================================================
1507 * Calls destructors on all components of the entity
1508 *============================================================================*/
1509static void ecs_destruct(ecs_t* ecs, ecs_id_t entity_id)
1510{
1511 // Load entity
1512 ecs_entity_data_t* entity = &ecs->entities[entity_id];
1513
1514 // Loop through components and call the destructors
1515 for (ecs_id_t comp_id = 0; comp_id < ecs->comp_count; comp_id++)
1516 {
1517 if (ecs_bitset_test(&entity->comp_bits, comp_id))
1518 {
1519 ecs_comp_data_t* comp = &ecs->comps[comp_id];
1520
1521 if (comp->destructor)
1522 {
1523 // Get component pointer directly without ecs_get to avoid
1524 // ready assertion, since entity may be queued for destruction
1525 ecs_comp_array_t* comp_array = &ecs->comp_arrays[comp_id];
1526 void* comp_ptr = (char*)comp_array->data + (comp_array->size * entity_id);
1527 ecs_entity_t entity = ecs_make_entity(entity_id);
1528
1529 comp->destructor(ecs, entity, comp_ptr);
1530 }
1531 }
1532 }
1533}
1534
1535/*=============================================================================
1536 * Functions to flush destroyed entity and removed component
1537 *============================================================================*/
1538static void ecs_flush_added(ecs_t* ecs, ecs_id_t sys_id)
1539{
1540 ecs_id_array_t* queue = &ecs->add_queue;
1541
1542 for (size_t i = 0; i < queue->size; i++)
1543 {
1544 ecs_id_t entity_id = queue->data[i];
1545
1546 ECS_ASSERT(ecs_is_active(ecs, entity_id));
1547 ecs_sparse_set_add(ecs, &ecs->systems[sys_id].entity_ids, entity_id);
1548 }
1549
1550 queue->size = 0;
1551}
1552
1553 static void ecs_flush_removed(ecs_t* ecs, ecs_id_t sys_id)
1554{
1555 ecs_id_array_t* remove_queue = &ecs->remove_queue;
1556
1557 for (size_t i = 0; i < remove_queue->size; i++)
1558 {
1559 ecs_id_t entity_id = remove_queue->data[i];
1560
1561 ECS_ASSERT(ecs_is_active(ecs, entity_id));
1562 ecs_sparse_set_remove(&ecs->systems[sys_id].entity_ids, entity_id);
1563 }
1564
1565 remove_queue->size = 0;
1566}
1567
1568static void ecs_flush_destroyed(ecs_t* ecs, ecs_id_t sys_id)
1569{
1570 ecs_id_array_t* destroy_queue = &ecs->destroy_queue;
1571
1572 for (size_t i = 0; i < destroy_queue->size; i++)
1573 {
1574 ecs_id_t entity_id = destroy_queue->data[i];
1575
1576 ECS_ASSERT(ecs_is_active(ecs, entity_id));
1577 ecs_sparse_set_remove(&ecs->systems[sys_id].entity_ids, entity_id);
1578
1579 // Push entity ID into pool
1580 ecs_id_array_t* pool = &ecs->entity_pool;
1581 ecs_id_array_push(ecs, pool, entity_id);
1582
1583 // Reset entity (sets bitset to 0 and, active and ready to false)
1584 ECS_MEMSET(&ecs->entities[entity_id], 0, sizeof(ecs_entity_data_t));
1585 }
1586
1587 destroy_queue->size = 0;
1588}
1589
1590
1591/*=============================================================================
1592 * Bitset functions
1593 *============================================================================*/
1594
1595#if ECS_MAX_COMPONENTS <= 64
1596
1597static inline bool ecs_bitset_is_zero(ecs_bitset_t* set)
1598{
1599 return *set == 0;
1600}
1601
1602static inline void ecs_bitset_flip(ecs_bitset_t* set, int bit, bool on)
1603{
1604 if (on)
1605 *set |= ((uint64_t)1 << bit);
1606 else
1607 *set &= ~((uint64_t)1 << bit);
1608}
1609
1610static inline bool ecs_bitset_test(ecs_bitset_t* set, int bit)
1611{
1612 return *set & ((uint64_t)1 << bit);
1613}
1614
1615static inline ecs_bitset_t ecs_bitset_and(ecs_bitset_t* set1, ecs_bitset_t* set2)
1616{
1617 return *set1 & *set2;
1618}
1619
1620static inline ecs_bitset_t ecs_bitset_or(ecs_bitset_t* set1, ecs_bitset_t* set2)
1621{
1622 return *set1 | *set2;
1623}
1624
1625static inline ecs_bitset_t ecs_bitset_not(ecs_bitset_t* set)
1626{
1627 return ~(*set);
1628}
1629
1630static inline bool ecs_bitset_equal(ecs_bitset_t* set1, ecs_bitset_t* set2)
1631{
1632 return *set1 == *set2;
1633}
1634
1635static inline bool ecs_bitset_true(ecs_bitset_t* set)
1636{
1637 return *set;
1638}
1639
1640#else // ECS_MAX_COMPONENTS
1641
1642static inline bool ecs_bitset_is_zero(ecs_bitset_t* set)
1643{
1644 for (int i = 0; i < ECS_BITSET_SIZE; i++)
1645 {
1646 if (set->array[i] != 0)
1647 return false;
1648 }
1649
1650 return true;
1651}
1652
1653static inline void ecs_bitset_flip(ecs_bitset_t* set, int bit, bool on)
1654{
1655 int index = bit / ECS_BITSET_WIDTH;
1656
1657 if (on)
1658 set->array[index] |= ((uint64_t)1 << bit % ECS_BITSET_WIDTH);
1659 else
1660 set->array[index] &= ~((uint64_t)1 << bit % ECS_BITSET_WIDTH);
1661}
1662
1663static inline bool ecs_bitset_test(ecs_bitset_t* set, int bit)
1664{
1665 int index = bit / ECS_BITSET_WIDTH;
1666 return set->array[index] & ((uint64_t)1 << bit % ECS_BITSET_WIDTH);
1667}
1668
1669static inline ecs_bitset_t ecs_bitset_and(ecs_bitset_t* set1,
1670 ecs_bitset_t* set2)
1671{
1672 ecs_bitset_t set;
1673
1674 for (int i = 0; i < ECS_BITSET_SIZE; i++)
1675 {
1676 set.array[i] = set1->array[i] & set2->array[i];
1677 }
1678
1679 return set;
1680}
1681
1682static inline ecs_bitset_t ecs_bitset_or(ecs_bitset_t* set1,
1683 ecs_bitset_t* set2)
1684{
1685 ecs_bitset_t set;
1686
1687 for (int i = 0; i < ECS_BITSET_SIZE; i++)
1688 {
1689 set.array[i] = set1->array[i] | set2->array[i];
1690 }
1691
1692 return set;
1693}
1694
1695static inline ecs_bitset_t ecs_bitset_not(ecs_bitset_t* set)
1696{
1697 ecs_bitset_t out;
1698
1699 for (int i = 0; i < ECS_BITSET_SIZE; i++)
1700 {
1701 out.array[i] = ~set->array[i];
1702 }
1703
1704 return out;
1705}
1706
1707static inline bool ecs_bitset_equal(ecs_bitset_t* set1, ecs_bitset_t* set2)
1708{
1709 for (int i = 0; i < ECS_BITSET_SIZE; i++)
1710 {
1711 if (set1->array[i] != set2->array[i])
1712 {
1713 return false;
1714 }
1715 }
1716
1717 return true;
1718}
1719
1720static inline bool ecs_bitset_true(ecs_bitset_t* set)
1721{
1722 for (int i = 0; i < ECS_BITSET_SIZE; i++)
1723 {
1724 if (set->array[i])
1725 return true;
1726 }
1727
1728 return false;
1729}
1730
1731#endif // ECS_MAX_COMPONENTS
1732
1733/*=============================================================================
1734 * Sparse set functions
1735 *============================================================================*/
1736
1737static void ecs_sparse_set_init(ecs_t* ecs, ecs_sparse_set_t* set, size_t capacity)
1738{
1739 ECS_ASSERT(ecs_is_not_null(ecs));
1740 ECS_ASSERT(ecs_is_not_null(set));
1741
1742 (void)ecs;
1743
1744 set->capacity = capacity;
1745 set->size = 0;
1746
1747 ECS_ASSERT(ecs_is_valid_capacity(capacity, sizeof(ecs_entity_t)));
1748 set->dense = (ecs_entity_t*)ECS_MALLOC(capacity * sizeof(ecs_entity_t), ecs->mem_ctx);
1749
1750 ECS_ASSERT(ecs_is_valid_capacity(capacity, sizeof(size_t)));
1751 set->sparse = (size_t*) ECS_MALLOC(capacity * sizeof(size_t), ecs->mem_ctx);
1752
1753 ECS_MEMSET(set->sparse, 0, capacity * sizeof(size_t));
1754}
1755
1756static void ecs_sparse_set_free(ecs_t* ecs, ecs_sparse_set_t* set)
1757{
1758 ECS_ASSERT(ecs_is_not_null(ecs));
1759 ECS_ASSERT(ecs_is_not_null(set));
1760
1761 (void)ecs;
1762
1763 ECS_FREE(set->dense, ecs->mem_ctx);
1764 ECS_FREE(set->sparse, ecs->mem_ctx);
1765}
1766
1767static bool ecs_sparse_set_add(ecs_t* ecs, ecs_sparse_set_t* set, ecs_id_t id)
1768{
1769 ECS_ASSERT(ecs_is_not_null(ecs));
1770 ECS_ASSERT(ecs_is_not_null(set));
1771 ECS_ASSERT(ecs_is_valid_id(id));
1772
1773 (void)ecs;
1774
1775 // Check if ID exists within the set
1776 if (ecs_sparse_set_find(set, id, NULL))
1777 return false;
1778
1779 // Grow sparse set if necessary
1780 if (id >= set->capacity)
1781 {
1782 size_t old_capacity = set->capacity;
1783 size_t new_capacity = old_capacity;
1784
1785 // Note that since a valid id doesn't have its high bit set, and
1786 // capacity is in terms of elements, doubling the capacity won't wrap
1787 do {
1788 new_capacity *= 2;
1789 } while (id >= new_capacity);
1790
1791
1792 // Grow dense array
1793 ECS_ASSERT(ecs_is_valid_capacity(set->capacity, sizeof(ecs_entity_t)));
1794 set->dense = (ecs_entity_t*)ecs_realloc_zero(ecs,
1795 set->dense,
1796 old_capacity * sizeof(ecs_entity_t),
1797 new_capacity * sizeof(ecs_entity_t));
1798
1799
1800 // Grow sparse array and zero it
1801 ECS_ASSERT(ecs_is_valid_capacity(set->capacity, sizeof(size_t)));
1802 set->sparse = (size_t*)ecs_realloc_zero(ecs,
1803 set->sparse,
1804 old_capacity * sizeof(size_t),
1805 new_capacity * sizeof(size_t));
1806
1807 // Set the new capacity
1808 set->capacity = new_capacity;
1809 }
1810
1811 // Add ID to set
1812 set->dense[set->size].id = id;
1813 set->sparse[id] = set->size;
1814 set->size++;
1815
1816 return true;
1817}
1818
1819static inline bool ecs_sparse_set_find(ecs_sparse_set_t* set, ecs_id_t id, size_t* found)
1820{
1821 ECS_ASSERT(ecs_is_not_null(set));
1822 ECS_ASSERT(ecs_is_valid_id(id));
1823
1824 if (id < set->capacity && set->sparse[id] < set->size && set->dense[set->sparse[id]].id == id)
1825 {
1826 if (found) *found = set->sparse[id];
1827 return true;
1828 }
1829 else
1830 {
1831 if (found) *found = 0;
1832 return false;
1833 }
1834}
1835
1836static inline bool ecs_sparse_set_remove(ecs_sparse_set_t* set, ecs_id_t id)
1837{
1838 ECS_ASSERT(ecs_is_not_null(set));
1839 ECS_ASSERT(ecs_is_valid_id(id));
1840
1841 if (!ecs_sparse_set_find(set, id, NULL))
1842 return false;
1843
1844 // Swap and remove (changes order of array)
1845 ecs_id_t tmp = set->dense[set->size - 1].id;
1846 set->dense[set->sparse[id]].id = tmp;
1847 set->sparse[tmp] = set->sparse[id];
1848
1849 set->size--;
1850
1851 return true;
1852}
1853
1854/*=============================================================================
1855 * System entity add/remove functions
1856 *============================================================================*/
1857
1858static inline bool ecs_entity_system_test(ecs_bitset_t* require_bits,
1859 ecs_bitset_t* exclude_bits,
1860 ecs_bitset_t* entity_bits)
1861{
1862 if (!ecs_bitset_is_zero(exclude_bits))
1863 {
1864 ecs_bitset_t overlap = ecs_bitset_and(entity_bits, exclude_bits);
1865
1866 if (ecs_bitset_true(&overlap))
1867 {
1868 return false;
1869 }
1870 }
1871
1872 ecs_bitset_t entity_and_require = ecs_bitset_and(entity_bits, require_bits);
1873 return ecs_bitset_equal(&entity_and_require, require_bits);
1874}
1875
1876static inline bool ecs_entity_system_remove_test(ecs_bitset_t* require_bits,
1877 ecs_bitset_t* exclude_bits,
1878 ecs_bitset_t* entity_bits)
1879{
1880 if (!ecs_bitset_is_zero(exclude_bits))
1881 {
1882 ecs_bitset_t overlap = ecs_bitset_and(entity_bits, exclude_bits);
1883
1884 if (ecs_bitset_true(&overlap))
1885 {
1886 return false;
1887 }
1888 }
1889
1890 ecs_bitset_t entity_and_require = ecs_bitset_and(entity_bits, require_bits);
1891 return ecs_bitset_true(&entity_and_require);
1892}
1893
1894/*=============================================================================
1895 * ID array functions
1896 *============================================================================*/
1897
1898static void ecs_id_array_init(ecs_t* ecs, ecs_id_array_t* array, size_t capacity)
1899{
1900 ECS_ASSERT(ecs_is_not_null(ecs));
1901 ECS_ASSERT(ecs_is_not_null(array));
1902
1903 (void)ecs;
1904
1905 array->size = 0;
1906 array->capacity = capacity;
1907
1908 ECS_ASSERT(ecs_is_valid_capacity(capacity, sizeof(ecs_id_t)));
1909 array->data = (ecs_id_t*)ECS_MALLOC(capacity * sizeof(ecs_id_t), ecs->mem_ctx);
1910}
1911
1912static void ecs_id_array_free(ecs_t* ecs, ecs_id_array_t* array)
1913{
1914 ECS_ASSERT(ecs_is_not_null(ecs));
1915 ECS_ASSERT(ecs_is_not_null(array));
1916
1917 (void)ecs;
1918
1919 ECS_FREE(array->data, ecs->mem_ctx);
1920}
1921
1922static inline void ecs_id_array_push(ecs_t* ecs, ecs_id_array_t* array, ecs_id_t id)
1923{
1924 ECS_ASSERT(ecs_is_not_null(ecs));
1925 ECS_ASSERT(ecs_is_not_null(array));
1926 ECS_ASSERT(ecs_is_valid_id(id));
1927
1928 (void)ecs;
1929
1930 if (array->size == array->capacity)
1931 {
1932
1933 // Note that since a valid id doesn't have its high bit set, and
1934 // capacity is in terms of elements, doubling the capacity won't wrap
1935 array->capacity *= 2;
1936
1937 ECS_ASSERT(ecs_is_valid_capacity(array->capacity, sizeof(ecs_id_t)));
1938 array->data = (ecs_id_t*)ECS_REALLOC(array->data,
1939 array->capacity * sizeof(ecs_id_t),
1940 ecs->mem_ctx);
1941 }
1942
1943 array->data[array->size++] = id;
1944}
1945
1946static inline ecs_id_t ecs_id_array_pop(ecs_id_array_t* array)
1947{
1948 ECS_ASSERT(ecs_is_not_null(array));
1949 return array->data[--array->size];
1950}
1951
1952static inline int ecs_id_array_size(ecs_id_array_t* array)
1953{
1954 return array->size;
1955}
1956
1957static void ecs_comp_array_init(ecs_t* ecs, ecs_comp_array_t* array, size_t size, size_t capacity)
1958{
1959 ECS_ASSERT(ecs_is_not_null(ecs));
1960 ECS_ASSERT(ecs_is_not_null(array));
1961
1962 (void)ecs;
1963
1964 ECS_MEMSET(array, 0, sizeof(ecs_comp_array_t));
1965
1966 array->capacity = capacity;
1967 array->size = size;
1968
1969 ECS_ASSERT(ecs_is_valid_capacity(capacity, size));
1970 array->data = ECS_MALLOC(capacity * size, ecs->mem_ctx);
1971}
1972
1973static void ecs_comp_array_free(ecs_t* ecs, ecs_comp_array_t* array)
1974{
1975 ECS_ASSERT(ecs_is_not_null(ecs));
1976 ECS_ASSERT(ecs_is_not_null(array));
1977
1978 (void)ecs;
1979
1980 ECS_FREE(array->data, ecs->mem_ctx);
1981}
1982
1983static void ecs_comp_array_resize(ecs_t* ecs, ecs_comp_array_t* array, ecs_id_t id)
1984{
1985 ECS_ASSERT(ecs_is_not_null(ecs));
1986 ECS_ASSERT(ecs_is_not_null(array));
1987 ECS_ASSERT(ecs_is_valid_id(id));
1988
1989 (void)ecs;
1990
1991 if (id >= array->capacity)
1992 {
1993 // Note that since a valid id doesn't have its high bit set, and
1994 // capacity is in terms of elements, doubling the capacity won't wrap
1995 do {
1996 array->capacity *= 2;
1997 } while (id >= array->capacity);
1998
1999 ECS_ASSERT(ecs_is_valid_capacity(array->capacity, array->size));
2000 array->data = ECS_REALLOC(array->data,
2001 array->capacity * array->size,
2002 ecs->mem_ctx);
2003 }
2004}
2005
2006/*=============================================================================
2007 * Validation functions
2008 *============================================================================*/
2009#ifndef NDEBUG
2010static bool ecs_is_not_null(void* ptr)
2011{
2012 return NULL != ptr;
2013}
2014
2015static bool ecs_is_valid_component_id(ecs_id_t id)
2016{
2017 return id < ECS_MAX_COMPONENTS;
2018}
2019
2020static bool ecs_is_valid_system_id(ecs_id_t id)
2021{
2022 return id < ECS_MAX_SYSTEMS;
2023}
2024
2025static bool ecs_is_valid_id(ecs_id_t id)
2026{
2027 // Ensures high bit is not set - works for any unsigned ecs_id_t
2028 return id == ((id << 1) >> 1);
2029}
2030
2031static bool ecs_is_valid_capacity(size_t capacity, size_t elem_size)
2032{
2033 // Ensures any array allocations won't overflow a signed size_t and are
2034 // nonzero. This is not the most efficient implementation, but it is simple
2035
2036 if (capacity == 0 || elem_size == 0)
2037 {
2038 return false;
2039 }
2040
2041 size_t max_cap = (SIZE_MAX >> 1) / elem_size;
2042 return capacity <= max_cap;
2043}
2044
2045static bool ecs_is_entity_ready(ecs_t* ecs, ecs_id_t entity_id)
2046{
2047 return ecs->entities[entity_id].ready;
2048}
2049
2050static bool ecs_is_component_ready(ecs_t* ecs, ecs_id_t comp_id)
2051{
2052 return comp_id < ecs->comp_count;
2053}
2054
2055static bool ecs_is_system_ready(ecs_t* ecs, ecs_id_t sys_id)
2056{
2057 return sys_id < ecs->system_count;
2058}
2059
2060#endif // NDEBUG
2061
2062#endif // PICO_ECS_IMPLEMENTATION
2063
2064/*
2065 ----------------------------------------------------------------------------
2066 This software is available under two licenses (A) or (B). You may choose
2067 either one as you wish:
2068 ----------------------------------------------------------------------------
2069
2070 (A) The zlib License
2071
2072 Copyright (c) 2025 James McLean
2073
2074 This software is provided 'as-is', without any express or implied warranty.
2075 In no event will the authors be held liable for any damages arising from the
2076 use of this software.
2077
2078 Permission is granted to anyone to use this software for any purpose,
2079 including commercial applications, and to alter it and redistribute it
2080 freely, subject to the following restrictions:
2081
2082 1. The origin of this software must not be misrepresented; you must not
2083 claim that you wrote the original software. If you use this software in a
2084 product, an acknowledgment in the product documentation would be appreciated
2085 but is not required.
2086
2087 2. Altered source versions must be plainly marked as such, and must not be
2088 misrepresented as being the original software.
2089
2090 3. This notice may not be removed or altered from any source distribution.
2091
2092 ----------------------------------------------------------------------------
2093
2094 (B) Public Domain (www.unlicense.org)
2095
2096 This is free and unencumbered software released into the public domain.
2097
2098 Anyone is free to copy, modify, publish, use, compile, sell, or distribute
2099 this software, either in source code form or as a compiled binary, for any
2100 purpose, commercial or non-commercial, and by any means.
2101
2102 In jurisdictions that recognize copyright laws, the author or authors of
2103 this software dedicate any and all copyright interest in the software to the
2104 public domain. We make this dedication for the benefit of the public at
2105 large and to the detriment of our heirs and successors. We intend this
2106 dedication to be an overt act of relinquishment in perpetuity of all present
2107 and future rights to this software under copyright law.
2108
2109 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2110 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2111 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2112 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
2113 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2114 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2115*/
2116
2117// EoF
void(* ecs_destructor_fn)(ecs_t *ecs, ecs_entity_t entity, void *comp_ptr)
Called when a component is destroyed (via ecs_remove or ecs_destroy)
Definition pico_ecs.h:272
void ecs_enable_system(ecs_t *ecs, ecs_system_t sys)
Enables a system.
#define ECS_INVALID_ID
An invalid ID.
Definition pico_ecs.h:224
void * ecs_add(ecs_t *ecs, ecs_entity_t entity, ecs_comp_t comp, void *args)
Adds a component instance to an entity.
#define ECS_MASK_TYPE
Determine mask type.
Definition pico_ecs.h:193
ecs_system_t ecs_define_system(ecs_t *ecs, ecs_mask_t mask, ecs_system_fn system_cb, ecs_added_fn add_cb, ecs_removed_fn remove_cb, void *udata)
Defines a system.
void * ecs_get(ecs_t *ecs, ecs_entity_t entity, ecs_comp_t comp)
Gets a component instance associated with an entity.
void ecs_remove(ecs_t *ecs, ecs_entity_t entity, ecs_comp_t comp)
Removes a component instance from an entity.
ECS_MASK_TYPE ecs_mask_t
Type for value used in system matching.
Definition pico_ecs.h:199
void ecs_exclude_component(ecs_t *ecs, ecs_system_t sys, ecs_comp_t comp)
Excludes entities having the specified component from being added to the target system.
void ecs_set_system_callbacks(ecs_t *ecs, ecs_system_t sys, ecs_system_fn system_cb, ecs_added_fn add_cb, ecs_removed_fn remove_cb)
Updates the callbacks for an existing system.
ecs_ret_t ecs_run_systems(ecs_t *ecs, ecs_mask_t mask)
Updates all systems.
ECS_ID_TYPE ecs_id_t
ID used for entity and components.
Definition pico_ecs.h:187
ecs_ret_t(* ecs_system_fn)(ecs_t *ecs, ecs_entity_t *entities, size_t entity_count, void *udata)
System callback.
Definition pico_ecs.h:305
void ecs_destroy(ecs_t *ecs, ecs_entity_t entity)
Destroys an entity.
ecs_comp_t ecs_define_component(ecs_t *ecs, size_t size, ecs_constructor_fn constructor, ecs_destructor_fn destructor)
Defines a component.
void ecs_free(ecs_t *ecs)
Destroys an ECS context.
void(* ecs_removed_fn)(ecs_t *ecs, ecs_entity_t entity, void *udata)
Called when an entity is removed from a system.
Definition pico_ecs.h:326
void * ecs_get_system_udata(ecs_t *ecs, ecs_system_t sys)
Gets the user data from a system.
void ecs_set_system_udata(ecs_t *ecs, ecs_system_t sys, void *udata)
Sets the user data for a system.
struct ecs_s ecs_t
ECS context.
Definition pico_ecs.h:175
void ecs_reset(ecs_t *ecs)
Removes all entities from the ECS, preserving systems and components.
bool ecs_is_ready(ecs_t *ecs, ecs_entity_t entity)
Returns true if the entity is currently active and has not been queued for destruction.
size_t ecs_get_system_entity_count(ecs_t *ecs, ecs_system_t sys)
Returns the number of entities assigned to the specified system.
void ecs_require_component(ecs_t *ecs, ecs_system_t sys, ecs_comp_t comp)
Entities are processed by the target system if they have all of the the components required by the sy...
ecs_ret_t ecs_run_system(ecs_t *ecs, ecs_system_t sys, ecs_mask_t mask)
Update an individual system.
void(* ecs_added_fn)(ecs_t *ecs, ecs_entity_t entity, void *udata)
Called when an entity is added to a system.
Definition pico_ecs.h:317
int32_t ecs_ret_t
Return code for system callback and calling functions.
Definition pico_ecs.h:204
void ecs_set_system_mask(ecs_t *ecs, ecs_system_t sys, ecs_mask_t mask)
Sets the system's mask.
void ecs_disable_system(ecs_t *ecs, ecs_system_t sys)
Disables a system.
bool ecs_has(ecs_t *ecs, ecs_entity_t entity, ecs_comp_t comp)
Test if entity has the specified component.
void(* ecs_constructor_fn)(ecs_t *ecs, ecs_entity_t entity, void *comp_ptr, void *args)
Called when a component is created (via ecs_add)
Definition pico_ecs.h:260
ecs_entity_t ecs_create(ecs_t *ecs)
Creates an entity.
ecs_t * ecs_new(size_t entity_count, void *mem_ctx)
Creates an ECS context.
#define ECS_ID_TYPE
Determine ID type. It should be unsigned.
Definition pico_ecs.h:181
ecs_mask_t ecs_get_system_mask(ecs_t *ecs, ecs_system_t sys)
Returns the system mask.
A component handle.
Definition pico_ecs.h:214
ecs_id_t id
Definition pico_ecs.h:214
An entity handle.
Definition pico_ecs.h:209
ecs_id_t id
Definition pico_ecs.h:209
A system handle.
Definition pico_ecs.h:219
ecs_id_t id
Definition pico_ecs.h:219