├── .clang-format ├── .editorconfig ├── .gitignore ├── Makefile ├── README.md ├── ecs.c ├── ecs.h ├── greatest.h ├── imgs ├── benchmark.png └── graph.png └── main.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | BreakBeforeBraces: Custom 3 | BraceWrapping: 4 | AfterExternBlock: true 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{c,h}] 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [Makefile] 12 | indent_style = tab 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | ecs_test 3 | vgcore.* 4 | callgrind.* 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -std=c99 -Werror -Wall -Wextra -pedantic-errors -I. 3 | DEPS = greatest.h ecs.h 4 | OBJ = main.o ecs.o 5 | 6 | all: $(OBJ) 7 | $(CC) -o ecs_test $^ $(CFLAGS) 8 | 9 | %.o: %.c $(DEPS) 10 | $(CC) -c -o $@ $< $(CFLAGS) 11 | 12 | .PHONY: clean 13 | clean: 14 | rm -f *.o ecs_test vgcore.* callgrind.* 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECS 2 | 3 | A small archetype based entity component system. I wrote this out of my own 4 | curiosity. This is not meant to be used in a "real" project. 5 | 6 | ## Usage 7 | 8 | Copy `ecs.c` and `ecs.h` to your project. 9 | 10 | ```c 11 | #include "ecs.h" 12 | #include 13 | 14 | typedef struct { 15 | float x; 16 | float y; 17 | } Position; 18 | 19 | typedef struct { 20 | float x; 21 | float y; 22 | } Velocity; 23 | 24 | void Move(ecs_view_t view, unsigned int row) { 25 | Position *p = ecs_view(view, row, 0); 26 | Velocity *v = ecs_view(view, row, 1); 27 | p->x += v->x; 28 | p->y += v->y; 29 | printf("Entity moved to (%f, %f)\n", p->x, p->y); 30 | } 31 | 32 | int main(int argc, char *argv[]) { 33 | ecs_registry_t *registry = ecs_init(); 34 | 35 | const ecs_entity_t pos_component = ECS_COMPONENT(registry, Position); 36 | const ecs_entity_t vel_component = ECS_COMPONENT(registry, Velocity); 37 | 38 | for (int i = 0; i < 10; i++) { 39 | ecs_entity_t e = ecs_entity(registry); 40 | ecs_attach(registry, e, pos_component); 41 | ecs_attach(registry, e, vel_component); 42 | ecs_set(registry, e, pos_component, &(Position){0.0f, 0.0f}); 43 | ecs_set(registry, e, vel_component, &(Velocity){1.0f, 1.0f}); 44 | } 45 | 46 | ECS_SYSTEM(registry, Move, 2, pos_component, vel_component); 47 | 48 | while (true) { 49 | ecs_step(registry); 50 | } 51 | 52 | ecs_destroy(registry); 53 | 54 | return 0; 55 | } 56 | ``` 57 | 58 | ## How it works 59 | 60 | Entity component systems lets you address performance and maintenance problems 61 | that typically arise in traditional object-oriented programming (OOP). Heavily 62 | nested hierarchies seen in OOP causes memory to be distributed all over the 63 | heap, forcing the CPU to twiddle its thumbs as it waits for data to be fetched 64 | from main memory. Object oriented inheritance hierarchies are also difficult to 65 | change as a project gets bigger and bigger. 66 | 67 | Entities are simply represented as 64 bit integers. They are stored in an 68 | archetype, a table where entity component data can be accessed. 69 | 70 | Components are just POD (Plain old data). They can be structs, or even integers. 71 | You can attach components to an entity. If you used Unity before, this kind of 72 | architecture may seem familiar to you. 73 | 74 | Systems are pieces of code that updates a given set of components. For example, 75 | a system that runs on `Position` and `Velocity` components will update all 76 | entities with at least those two components. 77 | 78 | Archetypes are a huge part of making this entity component system work as fast 79 | as it can. Each archetype stores a unique set of component types and component 80 | data. Archetypes are also vertices in a graph, with left and right edges. 81 | Traversing left in the graph leads to archetypes with one less component type, 82 | while traversing right finds archetypes with one additional component type. 83 | 84 | ![archetype graph](./imgs/graph.png) 85 | 86 | Storing archetypes in a graph is a fast approch to search for an archetype. 87 | Sander Mertens, the author of [Flecs](https://github.com/SanderMertens/flecs), 88 | explains this in depth in his [blog 89 | post](https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9). 90 | 91 | #### More Details 92 | 93 | - [Entities, components and systems](https://medium.com/ingeniouslysimple/entities-components-and-systems-89c31464240d) 94 | - [Building an ECS #2: Archetypes and Vectorization](https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9) 95 | 96 | ## Benchmarks 97 | 98 | The graph below shows the time it takes to update a given number of entities, 99 | 3600 times. Every entity has two components, and as a result, a single system is 100 | performing the operations. 101 | 102 | I benchmarked the ECS in this repository, 103 | [Flecs](https://github.com/SanderMertens/flecs), and 104 | [EnTT](https://github.com/skypjack/entt). 105 | 106 | ![ecs benchmark graph](./imgs/benchmark.png) 107 | 108 | | Entities | ecs | Flecs | EnTT | 109 | |----------|-----------|------------|------------| 110 | | 1 | 01 ms | 22 ms | 07 ms | 111 | | 2 | 01 ms | 13 ms | 09 ms | 112 | | 4 | 01 ms | 34 ms | 09 ms | 113 | | 8 | 01 ms | 27 ms | 25 ms | 114 | | 16 | 02 ms | 35 ms | 46 ms | 115 | | 32 | 03 ms | 45 ms | 84 ms | 116 | | 64 | 06 ms | 56 ms | 101 ms | 117 | | 128 | 09 ms | 75 ms | 134 ms | 118 | | 256 | 17 ms | 118 ms | 284 ms | 119 | | 512 | 34 ms | 173 ms | 561 ms | 120 | | 1,024 | 71 ms | 210 ms | 1,124 ms | 121 | | 2,048 | 133 ms | 438 ms | 2,181 ms | 122 | | 4,096 | 296 ms | 864 ms | 4,289 ms | 123 | | 8,192 | 586 ms | 1,633 ms | 8,513 ms | 124 | | 16,384 | 1,152 ms | 3,279 ms | 17,194 ms | 125 | | 32,768 | 2,357 ms | 6,427 ms | 34,019 ms | 126 | | 65,536 | 4,691 ms | 12,836 ms | 68,607 ms | 127 | | 131,072 | 9,990 ms | 26,230 ms | 137,298 ms | 128 | | 262,144 | 17,728 ms | 51,824 ms | 273,707 ms | 129 | | 524,288 | 37,713 ms | 103,477 ms | 556,364 ms | 130 | 131 | I couldn't get to benchmark over 1 million entities as EnTT crashed with the 132 | following message: 133 | 134 | ``` 135 | entt.hpp:8033: entt::basic_registry< >::entity_type entt::basic_registry< >::create() [with Entity = entt::entity; entt::basic_registry< >::entity_type = entt::entity]: Assertion `to_integral(entt) < traits_type::entity_mask' failed. 136 | ``` 137 | -------------------------------------------------------------------------------- /ecs.c: -------------------------------------------------------------------------------- 1 | #include "ecs.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define OUT_OF_MEMORY "out of memory" 9 | #define OUT_OF_BOUNDS "index out of bounds" 10 | #define FAILED_LOOKUP "lookup failed and returned null" 11 | #define NOT_IMPLEMENTED "not implemented" 12 | #define SOMETHING_TERRIBLE "something went terribly wrong" 13 | 14 | #define ECS_ABORT(error) \ 15 | fprintf(stderr, "ABORT %s:%d %s\n", __FILE__, __LINE__, error); \ 16 | abort(); 17 | 18 | #define ECS_ENSURE(cond, error) \ 19 | if (!(cond)) { \ 20 | fprintf(stderr, "condition not met: %s\n", #cond); \ 21 | ECS_ABORT(error); \ 22 | } 23 | 24 | #ifndef NDEBUG 25 | #define ECS_ASSERT(cond, error) ECS_ENSURE(cond, error); 26 | #else 27 | #define ECS_ASSERT(cond, error) 28 | #endif 29 | 30 | #define ECS_OFFSET(p, offset) ((void *)(((char *)(p)) + (offset))) 31 | 32 | static inline void *ecs_malloc(size_t bytes) { 33 | void *mem = malloc(bytes); 34 | ECS_ENSURE(mem != NULL, OUT_OF_MEMORY); 35 | return mem; 36 | } 37 | 38 | static inline void *ecs_calloc(size_t items, size_t bytes) { 39 | void *mem = calloc(items, bytes); 40 | ECS_ENSURE(mem != NULL, OUT_OF_MEMORY); 41 | return mem; 42 | } 43 | 44 | static inline void ecs_realloc(void **mem, size_t bytes) { 45 | *mem = realloc(*mem, bytes); 46 | if (bytes != 0) { 47 | ECS_ENSURE(*mem != NULL, OUT_OF_MEMORY); 48 | } 49 | } 50 | 51 | typedef struct ecs_bucket_t { 52 | const void *key; 53 | uint32_t index; 54 | } ecs_bucket_t; 55 | 56 | struct ecs_map_t { 57 | ecs_hash_fn hash; 58 | ecs_key_equal_fn key_equal; 59 | size_t key_size; 60 | size_t item_size; 61 | uint32_t count; 62 | uint32_t load_capacity; 63 | ecs_bucket_t *sparse; 64 | uint32_t *reverse_lookup; 65 | void *dense; 66 | }; 67 | 68 | struct ecs_type_t { 69 | uint32_t capacity; 70 | uint32_t count; 71 | ecs_entity_t *elements; 72 | }; 73 | 74 | struct ecs_signature_t { 75 | uint32_t count; 76 | ecs_entity_t components[]; 77 | }; 78 | 79 | typedef struct ecs_system_t { 80 | ecs_archetype_t *archetype; 81 | ecs_signature_t *sig; 82 | ecs_system_fn run; 83 | } ecs_system_t; 84 | 85 | struct ecs_edge_t { 86 | ecs_entity_t component; 87 | ecs_archetype_t *archetype; 88 | }; 89 | 90 | struct ecs_edge_list_t { 91 | uint32_t capacity; 92 | uint32_t count; 93 | ecs_edge_t *edges; 94 | }; 95 | 96 | typedef struct ecs_record_t { 97 | ecs_archetype_t *archetype; 98 | uint32_t row; 99 | } ecs_record_t; 100 | 101 | struct ecs_archetype_t { 102 | uint32_t capacity; 103 | uint32_t count; 104 | ecs_type_t *type; 105 | ecs_entity_t *entity_ids; 106 | void **components; 107 | ecs_edge_list_t *left_edges; 108 | ecs_edge_list_t *right_edges; 109 | }; 110 | 111 | struct ecs_registry_t { 112 | ecs_map_t *entity_index; // 113 | ecs_map_t *component_index; // 114 | ecs_map_t *system_index; // 115 | ecs_map_t *type_index; // 116 | ecs_archetype_t *root; 117 | ecs_entity_t next_entity_id; 118 | }; 119 | 120 | #define MAP_LOAD_FACTOR 0.5 121 | #define MAP_COLLISION_THRESHOLD 30 122 | #define MAP_TOMESTONE ((uint32_t)-1) 123 | 124 | ecs_map_t *ecs_map_new(size_t key_size, size_t item_size, ecs_hash_fn hash_fn, 125 | ecs_key_equal_fn key_equal_fn, uint32_t capacity) { 126 | ecs_map_t *map = ecs_malloc(sizeof(ecs_map_t)); 127 | map->hash = hash_fn; 128 | map->key_equal = key_equal_fn; 129 | map->sparse = ecs_calloc(sizeof(ecs_bucket_t), capacity); 130 | map->reverse_lookup = 131 | ecs_malloc(sizeof(uint32_t) * (capacity * MAP_LOAD_FACTOR + 1)); 132 | map->dense = ecs_malloc(item_size * (capacity * MAP_LOAD_FACTOR + 1)); 133 | map->key_size = key_size; 134 | map->item_size = item_size; 135 | map->load_capacity = capacity; 136 | map->count = 0; 137 | return map; 138 | } 139 | 140 | void ecs_map_free(ecs_map_t *map) { 141 | free(map->sparse); 142 | free(map->reverse_lookup); 143 | free(map->dense); 144 | free(map); 145 | } 146 | 147 | static inline uint32_t next_pow_of_2(uint32_t n) { 148 | n--; 149 | n |= n >> 1; 150 | n |= n >> 2; 151 | n |= n >> 4; 152 | n |= n >> 8; 153 | n |= n >> 16; 154 | n++; 155 | 156 | return n; 157 | } 158 | 159 | void *ecs_map_get(const ecs_map_t *map, const void *key) { 160 | uint32_t i = map->hash(key); 161 | ecs_bucket_t bucket = map->sparse[i % map->load_capacity]; 162 | uint32_t collisions = 0; 163 | 164 | while (bucket.index != 0) { 165 | if (map->key_equal(bucket.key, key) && bucket.index != MAP_TOMESTONE) { 166 | break; 167 | } 168 | 169 | i += next_pow_of_2(collisions++); 170 | bucket = map->sparse[i % map->load_capacity]; 171 | } 172 | 173 | if (bucket.index == 0 || bucket.index == MAP_TOMESTONE) { 174 | return NULL; 175 | } 176 | 177 | return ECS_OFFSET(map->dense, map->item_size * bucket.index); 178 | } 179 | 180 | static void ecs_map_grow(ecs_map_t *map, float growth_factor) { 181 | uint32_t new_capacity = map->load_capacity * growth_factor; 182 | ecs_bucket_t *new_sparse = ecs_calloc(sizeof(ecs_bucket_t), new_capacity); 183 | free(map->reverse_lookup); 184 | map->reverse_lookup = 185 | ecs_malloc(sizeof(uint32_t) * (new_capacity * MAP_LOAD_FACTOR + 1)); 186 | ecs_realloc(&map->dense, 187 | map->item_size * (new_capacity * MAP_LOAD_FACTOR + 1)); 188 | 189 | for (uint32_t i = 0; i < map->load_capacity; i++) { 190 | ecs_bucket_t bucket = map->sparse[i]; 191 | 192 | if (bucket.index != 0 && bucket.index != MAP_TOMESTONE) { 193 | uint32_t hashed = map->hash(bucket.key); 194 | ecs_bucket_t *other = &new_sparse[hashed % new_capacity]; 195 | uint32_t collisions = 0; 196 | 197 | while (other->index != 0) { 198 | hashed += next_pow_of_2(collisions++); 199 | other = &new_sparse[hashed % new_capacity]; 200 | } 201 | 202 | other->key = bucket.key; 203 | other->index = bucket.index; 204 | map->reverse_lookup[bucket.index] = hashed % new_capacity; 205 | } 206 | } 207 | 208 | free(map->sparse); 209 | map->sparse = new_sparse; 210 | map->load_capacity = new_capacity; 211 | } 212 | 213 | void ecs_map_set(ecs_map_t *map, const void *key, const void *payload) { 214 | uint32_t i = map->hash(key); 215 | ecs_bucket_t *bucket = &map->sparse[i % map->load_capacity]; 216 | uint32_t collisions = 0; 217 | ecs_bucket_t *first_tomestone = NULL; 218 | 219 | while (bucket->index != 0) { 220 | if (map->key_equal(bucket->key, key) && bucket->index != MAP_TOMESTONE) { 221 | void *loc = ECS_OFFSET(map->dense, map->item_size * bucket->index); 222 | memcpy(loc, payload, map->item_size); 223 | return; 224 | } 225 | 226 | if (!first_tomestone && bucket->index == MAP_TOMESTONE) { 227 | first_tomestone = bucket; 228 | } 229 | 230 | i += next_pow_of_2(collisions++); 231 | ECS_ASSERT(collisions < MAP_COLLISION_THRESHOLD, 232 | "too many hash collisions"); 233 | bucket = &map->sparse[i % map->load_capacity]; 234 | } 235 | 236 | if (first_tomestone) { 237 | bucket = first_tomestone; 238 | } 239 | 240 | bucket->key = key; 241 | bucket->index = map->count + 1; 242 | void *loc = ECS_OFFSET(map->dense, map->item_size * bucket->index); 243 | memcpy(loc, payload, map->item_size); 244 | map->reverse_lookup[bucket->index] = i % map->load_capacity; 245 | map->count++; 246 | 247 | if (map->count >= map->load_capacity * MAP_LOAD_FACTOR) { 248 | ecs_map_grow(map, 2); 249 | } 250 | } 251 | 252 | void ecs_map_remove(ecs_map_t *map, const void *key) { 253 | uint32_t i = map->hash(key); 254 | ecs_bucket_t bucket = map->sparse[i % map->load_capacity]; 255 | uint32_t next = 0; 256 | 257 | while (bucket.index != 0) { 258 | if (map->key_equal(bucket.key, key) && bucket.index != MAP_TOMESTONE) { 259 | break; 260 | } 261 | 262 | i += next_pow_of_2(next++); 263 | bucket = map->sparse[i % map->load_capacity]; 264 | } 265 | 266 | if (bucket.index == 0 || bucket.index == MAP_TOMESTONE) { 267 | return; 268 | } 269 | 270 | char *tmp[map->item_size]; 271 | void *left = ECS_OFFSET(map->dense, map->item_size * bucket.index); 272 | void *right = ECS_OFFSET(map->dense, map->item_size * map->count); 273 | memcpy(tmp, left, map->item_size); 274 | memcpy(left, right, map->item_size); 275 | memcpy(right, tmp, map->item_size); 276 | 277 | map->sparse[map->reverse_lookup[map->count]].index = bucket.index; 278 | map->sparse[map->reverse_lookup[bucket.index]].index = MAP_TOMESTONE; 279 | 280 | uint32_t reverse_tmp = map->reverse_lookup[bucket.index]; 281 | map->reverse_lookup[bucket.index] = map->reverse_lookup[map->count]; 282 | map->reverse_lookup[map->count] = reverse_tmp; 283 | 284 | map->count--; 285 | } 286 | 287 | void *ecs_map_values(ecs_map_t *map) { 288 | return ECS_OFFSET(map->dense, map->item_size); 289 | } 290 | 291 | uint32_t ecs_map_len(ecs_map_t *map) { return map->count; } 292 | 293 | uint32_t ecs_map_hash_intptr(const void *key) { 294 | uintptr_t hashed = (uintptr_t)key; 295 | hashed = ((hashed >> 16) ^ hashed) * 0x45d9f3b; 296 | hashed = ((hashed >> 16) ^ hashed) * 0x45d9f3b; 297 | hashed = (hashed >> 16) ^ hashed; 298 | return hashed; 299 | } 300 | 301 | uint32_t ecs_map_hash_string(const void *key) { 302 | char *str = (char *)key; 303 | uint32_t hash = 5381; 304 | char c; 305 | 306 | while ((c = *str++)) { 307 | hash = ((hash << 5) + hash) + c; 308 | } 309 | 310 | return hash; 311 | } 312 | 313 | uint32_t ecs_map_hash_type(const void *key) { 314 | ecs_type_t *type = (ecs_type_t *)key; 315 | uint32_t hash = 5381; 316 | ECS_TYPE_EACH(type, e, { hash = ((hash << 5) + hash) + e; }); 317 | return hash; 318 | } 319 | 320 | bool ecs_map_equal_intptr(const void *a, const void *b) { return a == b; } 321 | 322 | bool ecs_map_equal_string(const void *a, const void *b) { 323 | return strcmp(a, b) == 0; 324 | } 325 | 326 | bool ecs_map_equal_type(const void *a, const void *b) { 327 | return ecs_type_equal((ecs_type_t *)a, (ecs_type_t *)b); 328 | } 329 | 330 | #ifndef NDEBUG 331 | void ecs_map_inspect(ecs_map_t *map) { 332 | printf("\nmap: {\n" 333 | " item_size: %ld bytes\n" 334 | " count: %d items\n" 335 | " load_capacity: %d\n", 336 | map->item_size, map->count, map->load_capacity); 337 | 338 | printf(" sparse: [\n"); 339 | for (uint32_t i = 0; i < map->load_capacity; i++) { 340 | ecs_bucket_t bucket = map->sparse[i]; 341 | printf(" %d: { key: %lu, index: %d }\n", i, (uintptr_t)bucket.key, 342 | bucket.index); 343 | } 344 | printf(" ]\n"); 345 | 346 | printf(" dense: [\n"); 347 | for (uint32_t i = 0; i < map->load_capacity * MAP_LOAD_FACTOR + 1; i++) { 348 | if (i == map->count + 1) { 349 | printf(" -- end of load --\n"); 350 | } 351 | 352 | int item = *(int *)ECS_OFFSET(map->dense, map->item_size * i); 353 | printf(" %d: %d\n", i, item); 354 | } 355 | printf(" ]\n"); 356 | 357 | printf(" reverse_lookup: [\n"); 358 | for (uint32_t i = 0; i < map->load_capacity * MAP_LOAD_FACTOR + 1; i++) { 359 | if (i == map->count + 1) { 360 | printf(" -- end of load --\n"); 361 | } 362 | 363 | printf(" %d: %d\n", i, map->reverse_lookup[i]); 364 | } 365 | printf(" ]\n"); 366 | 367 | printf("}\n"); 368 | } 369 | #endif // NDEBUG 370 | 371 | ecs_type_t *ecs_type_new(uint32_t capacity) { 372 | ecs_type_t *type = ecs_malloc(sizeof(ecs_type_t)); 373 | type->elements = ecs_malloc(sizeof(ecs_entity_t) * capacity); 374 | type->capacity = capacity; 375 | type->count = 0; 376 | return type; 377 | } 378 | 379 | void ecs_type_free(ecs_type_t *type) { 380 | free(type->elements); 381 | free(type); 382 | } 383 | 384 | ecs_type_t *ecs_type_copy(const ecs_type_t *from) { 385 | ecs_type_t *type = ecs_malloc(sizeof(ecs_type_t)); 386 | type->elements = ecs_malloc(sizeof(ecs_entity_t) * from->capacity); 387 | type->capacity = from->capacity; 388 | type->count = from->count; 389 | memcpy(type->elements, from->elements, sizeof(ecs_entity_t) * from->count); 390 | return type; 391 | } 392 | 393 | uint32_t ecs_type_len(const ecs_type_t *type) { return type->count; } 394 | 395 | bool ecs_type_equal(const ecs_type_t *a, const ecs_type_t *b) { 396 | if (a == b) { 397 | return true; 398 | } 399 | 400 | if (a->count != b->count) { 401 | return false; 402 | } 403 | 404 | for (uint32_t i = 0; i < a->count; i++) { 405 | if (a->elements[i] != b->elements[i]) { 406 | return false; 407 | } 408 | } 409 | 410 | return true; 411 | } 412 | 413 | int32_t ecs_type_index_of(const ecs_type_t *type, ecs_entity_t e) { 414 | for (uint32_t i = 0; i < type->count; i++) { 415 | if (type->elements[i] == e) { 416 | return i; 417 | } 418 | } 419 | 420 | return -1; 421 | } 422 | 423 | void ecs_type_add(ecs_type_t *type, ecs_entity_t e) { 424 | if (type->count == type->capacity) { 425 | if (type->capacity == 0) { 426 | type->capacity = 1; 427 | } 428 | 429 | const uint32_t growth = 2; 430 | ecs_realloc((void **)&type->elements, 431 | sizeof(ecs_entity_t) * type->capacity * growth); 432 | type->capacity *= growth; 433 | } 434 | 435 | uint32_t i = 0; 436 | while (i < type->count && type->elements[i] < e) { 437 | i++; 438 | } 439 | 440 | if (i < type->count && type->elements[i] == e) { 441 | return; 442 | } 443 | 444 | ecs_entity_t held = e; 445 | ecs_entity_t tmp; 446 | while (i < type->count) { 447 | tmp = type->elements[i]; 448 | type->elements[i] = held; 449 | held = tmp; 450 | i++; 451 | } 452 | 453 | type->elements[i] = held; 454 | type->count++; 455 | } 456 | 457 | void ecs_type_remove(ecs_type_t *type, ecs_entity_t e) { 458 | uint32_t i = 0; 459 | while (i < type->count && type->elements[i] < e) { 460 | i++; 461 | } 462 | 463 | if (i == type->count || type->elements[i] != e) { 464 | return; 465 | } 466 | 467 | ECS_ASSERT(i < type->count, OUT_OF_BOUNDS); 468 | 469 | while (i < type->count - 1) { 470 | type->elements[i] = type->elements[i + 1]; 471 | i++; 472 | } 473 | 474 | type->count--; 475 | } 476 | 477 | bool ecs_type_is_superset(const ecs_type_t *super, const ecs_type_t *sub) { 478 | uint32_t left = 0, right = 0; 479 | uint32_t super_len = ecs_type_len(super); 480 | uint32_t sub_len = ecs_type_len(sub); 481 | 482 | if (super_len < sub_len) { 483 | return false; 484 | } 485 | 486 | while (left < super_len && right < sub_len) { 487 | if (super->elements[left] < sub->elements[right]) { 488 | left++; 489 | } else if (super->elements[left] == sub->elements[right]) { 490 | left++; 491 | right++; 492 | } else { 493 | return false; 494 | } 495 | } 496 | 497 | return right == sub_len; 498 | } 499 | 500 | #ifndef NDEBUG 501 | void ecs_type_inspect(ecs_type_t *type) { 502 | printf("\ntype: {\n"); 503 | printf(" capacity: %d\n", type->capacity); 504 | printf(" count: %d\n", type->count); 505 | 506 | printf(" elements: [\n"); 507 | for (uint32_t i = 0; i < type->count; i++) { 508 | printf(" %d: %lu\n", i, type->elements[i]); 509 | } 510 | printf(" ]\n"); 511 | 512 | printf("}\n"); 513 | } 514 | #endif 515 | 516 | ecs_signature_t *ecs_signature_new(uint32_t count) { 517 | ecs_signature_t *sig = 518 | ecs_malloc(sizeof(ecs_signature_t) + (sizeof(ecs_entity_t) * count)); 519 | sig->count = 0; 520 | return sig; 521 | } 522 | 523 | ecs_signature_t *ecs_signature_new_n(uint32_t count, ...) { 524 | ecs_signature_t *sig = ecs_signature_new(count); 525 | sig->count = count; 526 | 527 | va_list args; 528 | va_start(args, count); 529 | 530 | for (uint32_t i = 0; i < count; i++) { 531 | sig->components[i] = va_arg(args, ecs_entity_t); 532 | } 533 | 534 | va_end(args); 535 | 536 | return sig; 537 | } 538 | 539 | void ecs_signature_free(ecs_signature_t *sig) { free(sig); } 540 | 541 | ecs_type_t *ecs_signature_as_type(const ecs_signature_t *sig) { 542 | ecs_type_t *type = ecs_type_new(sig->count); 543 | 544 | for (uint32_t i = 0; i < sig->count; i++) { 545 | ecs_type_add(type, sig->components[i]); 546 | } 547 | 548 | return type; 549 | } 550 | 551 | ecs_edge_list_t *ecs_edge_list_new(void) { 552 | ecs_edge_list_t *edge_list = ecs_malloc(sizeof(ecs_edge_list_t)); 553 | edge_list->capacity = 8; 554 | edge_list->count = 0; 555 | edge_list->edges = ecs_malloc(sizeof(ecs_edge_t) * edge_list->capacity); 556 | return edge_list; 557 | } 558 | 559 | void ecs_edge_list_free(ecs_edge_list_t *edge_list) { 560 | free(edge_list->edges); 561 | free(edge_list); 562 | } 563 | 564 | uint32_t ecs_edge_list_len(const ecs_edge_list_t *edge_list) { 565 | return edge_list->count; 566 | } 567 | 568 | void ecs_edge_list_add(ecs_edge_list_t *edge_list, ecs_edge_t edge) { 569 | if (edge_list->count == edge_list->capacity) { 570 | const uint32_t growth = 2; 571 | ecs_realloc((void **)&edge_list->edges, 572 | sizeof(ecs_edge_t) * edge_list->capacity * growth); 573 | } 574 | 575 | edge_list->edges[edge_list->count++] = edge; 576 | } 577 | 578 | void ecs_edge_list_remove(ecs_edge_list_t *edge_list, ecs_entity_t component) { 579 | ecs_edge_t *edges = edge_list->edges; 580 | 581 | uint32_t i = 0; 582 | while (i < edge_list->count && edges[i].component != component) { 583 | i++; 584 | } 585 | 586 | if (i == edge_list->count) { 587 | return; 588 | } 589 | 590 | ecs_edge_t tmp = edges[i]; 591 | edges[i] = edges[edge_list->count]; 592 | edges[edge_list->count--] = tmp; 593 | } 594 | 595 | #define ARCHETYPE_INITIAL_CAPACITY 16 596 | 597 | static void 598 | ecs_archetype_resize_component_array(ecs_archetype_t *archetype, 599 | const ecs_map_t *component_index, 600 | uint32_t capacity) { 601 | uint32_t i = 0; 602 | ECS_TYPE_EACH(archetype->type, e, { 603 | size_t *component_size = ecs_map_get(component_index, (void *)e); 604 | ECS_ASSERT(component_size != NULL, FAILED_LOOKUP); 605 | ecs_realloc(&archetype->components[i], sizeof(*component_size) * capacity); 606 | archetype->capacity = capacity; 607 | i++; 608 | }); 609 | } 610 | 611 | ecs_archetype_t *ecs_archetype_new(ecs_type_t *type, 612 | const ecs_map_t *component_index, 613 | ecs_map_t *type_index) { 614 | ECS_ENSURE(ecs_map_get(type_index, type) == NULL, "archetype already exists"); 615 | 616 | ecs_archetype_t *archetype = ecs_malloc(sizeof(ecs_archetype_t)); 617 | 618 | archetype->capacity = ARCHETYPE_INITIAL_CAPACITY; 619 | archetype->count = 0; 620 | archetype->type = type; 621 | archetype->entity_ids = 622 | ecs_malloc(sizeof(ecs_entity_t) * ARCHETYPE_INITIAL_CAPACITY); 623 | archetype->components = ecs_calloc(sizeof(void *), ecs_type_len(type)); 624 | archetype->left_edges = ecs_edge_list_new(); 625 | archetype->right_edges = ecs_edge_list_new(); 626 | 627 | ecs_archetype_resize_component_array(archetype, component_index, 628 | ARCHETYPE_INITIAL_CAPACITY); 629 | ecs_map_set(type_index, type, &archetype); 630 | 631 | return archetype; 632 | } 633 | 634 | void ecs_archetype_free(ecs_archetype_t *archetype) { 635 | uint32_t component_count = ecs_type_len(archetype->type); 636 | for (uint32_t i = 0; i < component_count; i++) { 637 | free(archetype->components[i]); 638 | } 639 | free(archetype->components); 640 | 641 | ecs_type_free(archetype->type); 642 | ecs_edge_list_free(archetype->left_edges); 643 | ecs_edge_list_free(archetype->right_edges); 644 | free(archetype->entity_ids); 645 | free(archetype); 646 | } 647 | 648 | uint32_t ecs_archetype_add(ecs_archetype_t *archetype, 649 | const ecs_map_t *component_index, 650 | ecs_map_t *entity_index, ecs_entity_t e) { 651 | if (archetype->count == archetype->capacity) { 652 | const uint32_t growth = 2; 653 | ecs_realloc((void **)&archetype->entity_ids, 654 | sizeof(ecs_entity_t) * archetype->capacity * growth); 655 | ecs_archetype_resize_component_array(archetype, component_index, 656 | archetype->capacity * growth); 657 | } 658 | 659 | archetype->entity_ids[archetype->count] = e; 660 | ecs_map_set(entity_index, (void *)e, 661 | &(ecs_record_t){archetype, archetype->count}); 662 | 663 | return archetype->count++; 664 | } 665 | 666 | uint32_t ecs_archetype_move_entity_right(ecs_archetype_t *left, 667 | ecs_archetype_t *right, 668 | const ecs_map_t *component_index, 669 | ecs_map_t *entity_index, 670 | uint32_t left_row) { 671 | ECS_ASSERT(left_row < left->count, OUT_OF_BOUNDS); 672 | ecs_entity_t removed = left->entity_ids[left_row]; 673 | left->entity_ids[left_row] = left->entity_ids[left->count - 1]; 674 | 675 | uint32_t right_row = 676 | ecs_archetype_add(right, component_index, entity_index, removed); 677 | 678 | uint32_t i = 0, j = 0; 679 | ECS_TYPE_EACH(left->type, e, { 680 | ECS_ASSERT(e >= right->type->elements[j], "elements in types mismatched"); 681 | 682 | while (e != right->type->elements[j]) { 683 | j++; 684 | } 685 | 686 | size_t *component_size = ecs_map_get(component_index, (void *)e); 687 | ECS_ASSERT(component_size != NULL, FAILED_LOOKUP); 688 | void *left_component_array = left->components[i]; 689 | void *right_component_array = right->components[j]; 690 | 691 | void *insert_component = 692 | ECS_OFFSET(right_component_array, *component_size * right_row); 693 | void *remove_component = 694 | ECS_OFFSET(left_component_array, *component_size * left_row); 695 | void *swap_component = 696 | ECS_OFFSET(left_component_array, *component_size * (left->count - 1)); 697 | 698 | memcpy(insert_component, remove_component, *component_size); 699 | memcpy(remove_component, swap_component, *component_size); 700 | 701 | i++; 702 | }); 703 | 704 | left->count--; 705 | return right_row; 706 | } 707 | 708 | static inline void ecs_archetype_make_edges(ecs_archetype_t *left, 709 | ecs_archetype_t *right, 710 | ecs_entity_t component) { 711 | ecs_edge_list_add(left->right_edges, (ecs_edge_t){component, right}); 712 | ecs_edge_list_add(right->left_edges, (ecs_edge_t){component, left}); 713 | } 714 | 715 | static void ecs_archetype_insert_vertex_help(ecs_archetype_t *node, 716 | ecs_archetype_t *new_node) { 717 | uint32_t node_type_len = ecs_type_len(node->type); 718 | uint32_t new_type_len = ecs_type_len(new_node->type); 719 | 720 | if (node_type_len > new_type_len - 1) { 721 | return; 722 | } 723 | 724 | if (node_type_len < new_type_len - 1) { 725 | ECS_EDGE_LIST_EACH(node->right_edges, edge, { 726 | ecs_archetype_insert_vertex_help(edge.archetype, new_node); 727 | }); 728 | return; 729 | } 730 | 731 | if (!ecs_type_is_superset(node->type, new_node->type)) { 732 | return; 733 | } 734 | 735 | uint32_t i; 736 | uint32_t new_node_type_len = ecs_type_len(new_node->type); 737 | for (i = 0; i < new_node_type_len && 738 | node->type->elements[i] == new_node->type->elements[i]; 739 | i++) 740 | ; 741 | ecs_archetype_make_edges(new_node, node, node->type->elements[i]); 742 | } 743 | 744 | ecs_archetype_t *ecs_archetype_insert_vertex(ecs_archetype_t *root, 745 | ecs_archetype_t *left_neighbour, 746 | ecs_type_t *new_vertex_type, 747 | ecs_entity_t component_for_edge, 748 | const ecs_map_t *component_index, 749 | ecs_map_t *type_index) { 750 | ecs_archetype_t *vertex = 751 | ecs_archetype_new(new_vertex_type, component_index, type_index); 752 | ecs_archetype_make_edges(left_neighbour, vertex, component_for_edge); 753 | ecs_archetype_insert_vertex_help(root, vertex); 754 | return vertex; 755 | } 756 | 757 | static ecs_archetype_t *ecs_archetype_traverse_and_create_help( 758 | ecs_archetype_t *vertex, const ecs_type_t *type, uint32_t stack_n, 759 | ecs_entity_t acc[], uint32_t acc_top, ecs_archetype_t *root, 760 | const ecs_map_t *component_index, ecs_map_t *type_index) { 761 | if (stack_n == 0) { 762 | ECS_ASSERT(ecs_type_equal(vertex->type, type), SOMETHING_TERRIBLE); 763 | return vertex; 764 | } 765 | 766 | ECS_EDGE_LIST_EACH(vertex->right_edges, edge, { 767 | if (ecs_type_index_of(type, edge.component) != -1) { 768 | acc[acc_top] = edge.component; 769 | return ecs_archetype_traverse_and_create_help( 770 | edge.archetype, type, stack_n - 1, acc, acc_top + 1, root, 771 | component_index, type_index); 772 | } 773 | }); 774 | 775 | uint32_t i; 776 | ecs_type_t *new_type = ecs_type_new(acc_top); 777 | for (i = 0; i < acc_top; i++) { 778 | ecs_type_add(new_type, acc[i]); 779 | } 780 | 781 | i = 0; 782 | ecs_entity_t new_component = 0; 783 | ECS_TYPE_EACH(type, e, { 784 | if (e != new_type->elements[i]) { 785 | new_component = e; 786 | ecs_type_add(new_type, new_component); 787 | acc[acc_top] = new_component; 788 | break; 789 | } 790 | 791 | i++; 792 | }); 793 | 794 | ECS_ASSERT(new_component != 0, SOMETHING_TERRIBLE); 795 | ecs_archetype_t *new_vertex = ecs_archetype_insert_vertex( 796 | root, vertex, new_type, new_component, component_index, type_index); 797 | 798 | return ecs_archetype_traverse_and_create_help(new_vertex, type, stack_n - 1, 799 | acc, acc_top + 1, root, 800 | component_index, type_index); 801 | } 802 | 803 | ecs_archetype_t * 804 | ecs_archetype_traverse_and_create(ecs_archetype_t *root, const ecs_type_t *type, 805 | const ecs_map_t *component_index, 806 | ecs_map_t *type_index) { 807 | uint32_t len = ecs_type_len(type); 808 | ecs_entity_t *acc = alloca(sizeof(ecs_entity_t) * len); 809 | return ecs_archetype_traverse_and_create_help(root, type, len, acc, 0, root, 810 | component_index, type_index); 811 | } 812 | 813 | #ifndef NDEBUG 814 | void ecs_archetype_inspect(ecs_archetype_t *archetype) { 815 | printf("\narchetype: {\n"); 816 | printf(" self: %p\n", (void *)archetype); 817 | printf(" capacity: %d\n", archetype->capacity); 818 | printf(" count: %d\n", archetype->count); 819 | 820 | printf(" type: %p\n", (void *)archetype->type); 821 | printf(" type: [ "); 822 | ECS_TYPE_EACH(archetype->type, e, { printf("%lu ", e); }); 823 | printf("]\n"); 824 | 825 | printf(" entity_ids: [\n"); 826 | for (uint32_t i = 0; i < archetype->count; i++) { 827 | printf(" %lu\n", archetype->entity_ids[i]); 828 | } 829 | printf(" ]\n"); 830 | 831 | printf(" left_edges: [\n"); 832 | ECS_EDGE_LIST_EACH(archetype->left_edges, edge, { 833 | printf(" { %lu, %p }\n", edge.component, (void *)edge.archetype); 834 | }); 835 | printf(" ]\n"); 836 | 837 | printf(" right_edges: [\n"); 838 | ECS_EDGE_LIST_EACH(archetype->right_edges, edge, { 839 | printf(" { %lu, %p }\n", edge.component, (void *)edge.archetype); 840 | }); 841 | printf(" ]\n"); 842 | 843 | printf(" components: [\n"); 844 | uint32_t type_len = ecs_type_len(archetype->type); 845 | for (uint32_t i = 0; i < type_len; i++) { 846 | printf(" %d: [\n", i); 847 | for (uint32_t j = 0; j < archetype->capacity; j++) { 848 | if (j == archetype->count) { 849 | printf(" -- end of load --\n"); 850 | } 851 | printf(" %d: %f\n", j, 852 | *(float *)ECS_OFFSET(archetype->components[i], j * sizeof(float))); 853 | } 854 | printf(" ]\n"); 855 | } 856 | printf(" ]\n"); 857 | 858 | printf("}\n"); 859 | } 860 | #endif 861 | 862 | ecs_registry_t *ecs_init(void) { 863 | ecs_registry_t *registry = ecs_malloc(sizeof(ecs_registry_t)); 864 | registry->entity_index = ECS_MAP(intptr, ecs_entity_t, ecs_record_t, 16); 865 | registry->component_index = ECS_MAP(intptr, ecs_entity_t, size_t, 8); 866 | registry->system_index = ECS_MAP(intptr, ecs_entity_t, ecs_system_t, 4); 867 | registry->type_index = ECS_MAP(type, ecs_type_t *, ecs_archetype_t *, 8); 868 | 869 | ecs_type_t *root_type = ecs_type_new(0); 870 | registry->root = ecs_archetype_new(root_type, registry->component_index, 871 | registry->type_index); 872 | registry->next_entity_id = 1; 873 | return registry; 874 | } 875 | 876 | void ecs_destroy(ecs_registry_t *registry) { 877 | ECS_MAP_VALUES_EACH(registry->system_index, ecs_system_t, system, 878 | { ecs_signature_free(system->sig); }); 879 | ECS_MAP_VALUES_EACH(registry->type_index, ecs_archetype_t *, archetype, 880 | { ecs_archetype_free(*archetype); }); 881 | ecs_map_free(registry->type_index); 882 | ecs_map_free(registry->entity_index); 883 | ecs_map_free(registry->component_index); 884 | ecs_map_free(registry->system_index); 885 | free(registry); 886 | } 887 | 888 | ecs_entity_t ecs_entity(ecs_registry_t *registry) { 889 | ecs_archetype_t *root = registry->root; 890 | uint32_t row = 891 | ecs_archetype_add(root, registry->component_index, registry->entity_index, 892 | registry->next_entity_id); 893 | ecs_map_set(registry->entity_index, (void *)registry->next_entity_id, 894 | &(ecs_record_t){root, row}); 895 | return registry->next_entity_id++; 896 | } 897 | 898 | ecs_entity_t ecs_component(ecs_registry_t *registry, size_t component_size) { 899 | ecs_map_set(registry->component_index, (void *)registry->next_entity_id, 900 | &(size_t){component_size}); 901 | return registry->next_entity_id++; 902 | } 903 | 904 | ecs_entity_t ecs_system(ecs_registry_t *registry, ecs_signature_t *signature, 905 | ecs_system_fn system) { 906 | ecs_type_t *type = ecs_signature_as_type(signature); 907 | ecs_archetype_t **maybe_archetype = ecs_map_get(registry->type_index, type); 908 | ecs_archetype_t *archetype; 909 | 910 | if (maybe_archetype == NULL) { 911 | archetype = ecs_archetype_traverse_and_create( 912 | registry->root, type, registry->component_index, registry->type_index); 913 | } else { 914 | archetype = *maybe_archetype; 915 | ecs_type_free(type); 916 | } 917 | 918 | ecs_map_set(registry->system_index, (void *)registry->next_entity_id, 919 | &(ecs_system_t){archetype, signature, system}); 920 | return registry->next_entity_id++; 921 | } 922 | 923 | void ecs_attach(ecs_registry_t *registry, ecs_entity_t entity, 924 | ecs_entity_t component) { 925 | ecs_record_t *record = ecs_map_get(registry->entity_index, (void *)entity); 926 | 927 | if (record == NULL) { 928 | char err[255]; 929 | sprintf(err, "attaching component %lu to unknown entity %lu", component, 930 | entity); 931 | ECS_ABORT(err); 932 | } 933 | 934 | ecs_type_t *init_type = record->archetype->type; 935 | ecs_type_t *fini_type = ecs_type_copy(init_type); 936 | ecs_type_add(fini_type, component); 937 | 938 | ecs_archetype_t **maybe_fini_archetype = 939 | ecs_map_get(registry->type_index, fini_type); 940 | ecs_archetype_t *fini_archetype; 941 | 942 | if (maybe_fini_archetype == NULL) { 943 | fini_archetype = ecs_archetype_insert_vertex( 944 | registry->root, record->archetype, fini_type, component, 945 | registry->component_index, registry->type_index); 946 | } else { 947 | ecs_type_free(fini_type); 948 | fini_archetype = *maybe_fini_archetype; 949 | } 950 | 951 | uint32_t new_row = ecs_archetype_move_entity_right( 952 | record->archetype, fini_archetype, registry->component_index, 953 | registry->entity_index, record->row); 954 | ecs_map_set(registry->entity_index, (void *)entity, 955 | &(ecs_record_t){fini_archetype, new_row}); 956 | } 957 | 958 | void ecs_set(ecs_registry_t *registry, ecs_entity_t entity, 959 | ecs_entity_t component, const void *data) { 960 | size_t *component_size = 961 | ecs_map_get(registry->component_index, (void *)component); 962 | ECS_ENSURE(component_size != NULL, FAILED_LOOKUP); 963 | 964 | ecs_record_t *record = ecs_map_get(registry->entity_index, (void *)entity); 965 | ECS_ENSURE(record != NULL, FAILED_LOOKUP); 966 | 967 | int32_t column = ecs_type_index_of(record->archetype->type, component); 968 | ECS_ENSURE(column != -1, OUT_OF_BOUNDS); 969 | 970 | void *component_array = record->archetype->components[column]; 971 | void *element = ECS_OFFSET(component_array, *component_size * record->row); 972 | memcpy(element, data, *component_size); 973 | } 974 | 975 | static void ecs_step_help(ecs_archetype_t *archetype, 976 | const ecs_map_t *component_index, 977 | const ecs_signature_t *sig, ecs_system_fn run) { 978 | if (archetype == NULL) { 979 | return; 980 | } 981 | 982 | uint32_t signature_to_index[sig->count]; 983 | uint32_t component_sizes[sig->count]; 984 | 985 | for (uint32_t slow = 0; slow < sig->count; slow++) { 986 | uint32_t type_len = ecs_type_len(archetype->type); 987 | for (uint32_t fast = 0; fast < type_len; fast++) { 988 | ecs_entity_t component = archetype->type->elements[fast]; 989 | if (component == sig->components[slow]) { 990 | size_t *component_size = 991 | ecs_map_get(component_index, (void *)component); 992 | ECS_ENSURE(component_size != NULL, FAILED_LOOKUP); 993 | 994 | component_sizes[slow] = *component_size; 995 | signature_to_index[slow] = fast; 996 | break; 997 | } 998 | } 999 | } 1000 | 1001 | ecs_view_t view = {archetype->components, signature_to_index, 1002 | component_sizes}; 1003 | for (uint32_t i = 0; i < archetype->count; i++) { 1004 | run(view, i); 1005 | } 1006 | 1007 | ECS_EDGE_LIST_EACH(archetype->right_edges, edge, { 1008 | ecs_step_help(edge.archetype, component_index, sig, run); 1009 | }); 1010 | } 1011 | 1012 | void ecs_step(ecs_registry_t *registry) { 1013 | ECS_MAP_VALUES_EACH(registry->system_index, ecs_system_t, sys, { 1014 | ecs_step_help(sys->archetype, registry->component_index, sys->sig, 1015 | sys->run); 1016 | }); 1017 | } 1018 | 1019 | void *ecs_view(ecs_view_t view, uint32_t row, uint32_t column) { 1020 | void *component_array = 1021 | view.component_arrays[view.signature_to_index[column]]; 1022 | return ECS_OFFSET(component_array, view.component_sizes[column] * row); 1023 | } 1024 | -------------------------------------------------------------------------------- /ecs.h: -------------------------------------------------------------------------------- 1 | #ifndef ECS_H 2 | #define ECS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" 10 | { 11 | #endif 12 | 13 | typedef uintptr_t ecs_entity_t; 14 | 15 | // -- MAP -------------------------------------------------------------------- 16 | // type unsafe hashtable 17 | 18 | typedef struct ecs_map_t ecs_map_t; 19 | 20 | typedef uint32_t (*ecs_hash_fn)(const void *); 21 | typedef bool (*ecs_key_equal_fn)(const void *, const void *); 22 | 23 | #define ECS_MAP(fn, k, v, capacity) \ 24 | ecs_map_new(sizeof(k), sizeof(v), ecs_map_hash_##fn, ecs_map_equal_##fn, \ 25 | capacity) 26 | 27 | ecs_map_t *ecs_map_new(size_t key_size, size_t item_size, ecs_hash_fn hash_fn, 28 | ecs_key_equal_fn key_equal_fn, uint32_t capacity); 29 | void ecs_map_free(ecs_map_t *map); 30 | void *ecs_map_get(const ecs_map_t *map, const void *key); 31 | void ecs_map_set(ecs_map_t *map, const void *key, const void *payload); 32 | void ecs_map_remove(ecs_map_t *map, const void *key); 33 | void *ecs_map_values(ecs_map_t *map); 34 | uint32_t ecs_map_len(ecs_map_t *map); 35 | uint32_t ecs_map_hash_intptr(const void *key); 36 | uint32_t ecs_map_hash_string(const void *key); 37 | uint32_t ecs_map_hash_type(const void *key); 38 | bool ecs_map_equal_intptr(const void *a, const void *b); 39 | bool ecs_map_equal_string(const void *a, const void *b); 40 | bool ecs_map_equal_type(const void *a, const void *b); 41 | 42 | #define ECS_MAP_VALUES_EACH(map, T, var, ...) \ 43 | do { \ 44 | uint32_t var##_count = ecs_map_len(map); \ 45 | T *var##_values = ecs_map_values(map); \ 46 | for (uint32_t var##_i = 0; var##_i < var##_count; var##_i++) { \ 47 | T *var = &var##_values[var##_i]; \ 48 | __VA_ARGS__ \ 49 | } \ 50 | } while (0) 51 | 52 | #ifndef NDEBUG 53 | void ecs_map_inspect(ecs_map_t *map); // assumes keys and values are ints 54 | #endif 55 | 56 | // -- TYPE ------------------------------------------------------------------- 57 | // set of component ids in sorted order 58 | 59 | typedef struct ecs_type_t ecs_type_t; 60 | 61 | ecs_type_t *ecs_type_new(uint32_t capacity); 62 | void ecs_type_free(ecs_type_t *type); 63 | ecs_type_t *ecs_type_copy(const ecs_type_t *from); 64 | uint32_t ecs_type_len(const ecs_type_t *type); 65 | bool ecs_type_equal(const ecs_type_t *a, const ecs_type_t *b); 66 | int32_t ecs_type_index_of(const ecs_type_t *type, ecs_entity_t e); 67 | void ecs_type_add(ecs_type_t *type, ecs_entity_t e); 68 | void ecs_type_remove(ecs_type_t *type, ecs_entity_t e); 69 | bool ecs_type_is_superset(const ecs_type_t *super, const ecs_type_t *sub); 70 | 71 | #define ECS_TYPE_ADD(type, e, s) \ 72 | ecs_type_add(type, (ecs_component_t){e, sizeof(s)}); 73 | 74 | #define ECS_TYPE_EACH(type, var, ...) \ 75 | do { \ 76 | uint32_t var##_count = ecs_type_len(type); \ 77 | for (uint32_t var##_i = 0; var##_i < var##_count; var##_i++) { \ 78 | ecs_entity_t var = type->elements[var##_i]; \ 79 | __VA_ARGS__ \ 80 | } \ 81 | } while (0) 82 | 83 | #ifndef NDEBUG 84 | void ecs_type_inspect(ecs_type_t *type); 85 | #endif 86 | 87 | // -- SIGNATURE -------------------------------------------------------------- 88 | // component ids in a defined order 89 | 90 | typedef struct ecs_signature_t ecs_signature_t; 91 | 92 | ecs_signature_t *ecs_signature_new(uint32_t count); 93 | ecs_signature_t *ecs_signature_new_n(uint32_t count, ...); 94 | void ecs_signature_free(ecs_signature_t *sig); 95 | ecs_type_t *ecs_signature_as_type(const ecs_signature_t *sig); 96 | 97 | // -- EDGE LIST -------------------------------------------------------------- 98 | // archetype edges for graph traversal 99 | 100 | typedef struct ecs_edge_t ecs_edge_t; 101 | typedef struct ecs_edge_list_t ecs_edge_list_t; 102 | 103 | ecs_edge_list_t *ecs_edge_list_new(void); 104 | void ecs_edge_list_free(ecs_edge_list_t *edge_list); 105 | uint32_t ecs_edge_list_len(const ecs_edge_list_t *edge_list); 106 | void ecs_edge_list_add(ecs_edge_list_t *edge_list, ecs_edge_t edge); 107 | void ecs_edge_list_remove(ecs_edge_list_t *edge_list, ecs_entity_t component); 108 | 109 | #define ECS_EDGE_LIST_EACH(edge_list, var, ...) \ 110 | do { \ 111 | uint32_t var##_count = ecs_edge_list_len(edge_list); \ 112 | for (uint32_t var##_i = 0; var##_i < var##_count; var##_i++) { \ 113 | ecs_edge_t var = edge_list->edges[var##_i]; \ 114 | __VA_ARGS__ \ 115 | } \ 116 | } while (0) 117 | 118 | // -- ARCHETYPE -------------------------------------------------------------- 119 | // graph vertex. archetypes are tables where columns represent component data 120 | // and rows represent each entity. left edges point to other archetypes with 121 | // one less component, and right edges point to archetypes that store one 122 | // additional component. 123 | 124 | typedef struct ecs_archetype_t ecs_archetype_t; 125 | 126 | ecs_archetype_t *ecs_archetype_new(ecs_type_t *type, 127 | const ecs_map_t *component_index, 128 | ecs_map_t *type_index); 129 | void ecs_archetype_free(ecs_archetype_t *archetype); 130 | uint32_t ecs_archetype_add(ecs_archetype_t *archetype, 131 | const ecs_map_t *component_index, 132 | ecs_map_t *entity_index, ecs_entity_t e); 133 | uint32_t ecs_archetype_move_entity_right(ecs_archetype_t *left, 134 | ecs_archetype_t *right, 135 | const ecs_map_t *component_index, 136 | ecs_map_t *entity_index, 137 | uint32_t left_row); 138 | ecs_archetype_t *ecs_archetype_insert_vertex(ecs_archetype_t *root, 139 | ecs_archetype_t *left_neighbour, 140 | ecs_type_t *new_vertex_type, 141 | ecs_entity_t component_for_edge, 142 | const ecs_map_t *component_index, 143 | ecs_map_t *type_index); 144 | ecs_archetype_t *ecs_archetype_traverse_and_create( 145 | ecs_archetype_t *root, const ecs_type_t *type, 146 | const ecs_map_t *component_index, ecs_map_t *type_index); 147 | 148 | #ifndef NDEBUG 149 | void ecs_archetype_inspect(ecs_archetype_t *archetype); 150 | #endif 151 | 152 | // -- ENTITY COMPONENT SYSTEM ------------------------------------------------ 153 | // functions below is the intended public api 154 | 155 | typedef struct ecs_view_t { 156 | void **component_arrays; 157 | uint32_t *signature_to_index; 158 | uint32_t *component_sizes; 159 | } ecs_view_t; 160 | 161 | typedef void (*ecs_system_fn)(ecs_view_t, uint32_t); 162 | 163 | typedef struct ecs_registry_t ecs_registry_t; 164 | 165 | ecs_registry_t *ecs_init(void); 166 | void ecs_destroy(ecs_registry_t *registry); 167 | ecs_entity_t ecs_entity(ecs_registry_t *registry); 168 | ecs_entity_t ecs_component(ecs_registry_t *registry, size_t component_size); 169 | ecs_entity_t ecs_system(ecs_registry_t *registry, ecs_signature_t *signature, 170 | ecs_system_fn system); 171 | void ecs_attach(ecs_registry_t *registry, ecs_entity_t entity, 172 | ecs_entity_t component); 173 | void ecs_set(ecs_registry_t *registry, ecs_entity_t entity, 174 | ecs_entity_t component, const void *data); 175 | void ecs_step(ecs_registry_t *registry); 176 | void *ecs_view(ecs_view_t view, uint32_t row, uint32_t column); 177 | 178 | #define ECS_COMPONENT(registry, T) ecs_component(registry, sizeof(T)); 179 | #define ECS_SYSTEM(registry, system, n, ...) \ 180 | ecs_system(registry, ecs_signature_new_n(n, __VA_ARGS__), system) 181 | 182 | #ifdef __cplusplus 183 | } // extern "C" 184 | #endif 185 | 186 | #endif // ECS_H 187 | -------------------------------------------------------------------------------- /greatest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2019 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef GREATEST_H 18 | #define GREATEST_H 19 | 20 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 21 | extern "C" { 22 | #endif 23 | 24 | /* 1.4.2 */ 25 | #define GREATEST_VERSION_MAJOR 1 26 | #define GREATEST_VERSION_MINOR 4 27 | #define GREATEST_VERSION_PATCH 2 28 | 29 | /* A unit testing system for C, contained in 1 file. 30 | * It doesn't use dynamic allocation or depend on anything 31 | * beyond ANSI C89. 32 | * 33 | * An up-to-date version can be found at: 34 | * https://github.com/silentbicycle/greatest/ 35 | */ 36 | 37 | 38 | /********************************************************************* 39 | * Minimal test runner template 40 | *********************************************************************/ 41 | #if 0 42 | 43 | #include "greatest.h" 44 | 45 | TEST foo_should_foo(void) { 46 | PASS(); 47 | } 48 | 49 | static void setup_cb(void *data) { 50 | printf("setup callback for each test case\n"); 51 | } 52 | 53 | static void teardown_cb(void *data) { 54 | printf("teardown callback for each test case\n"); 55 | } 56 | 57 | SUITE(suite) { 58 | /* Optional setup/teardown callbacks which will be run before/after 59 | * every test case. If using a test suite, they will be cleared when 60 | * the suite finishes. */ 61 | SET_SETUP(setup_cb, voidp_to_callback_data); 62 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data); 63 | 64 | RUN_TEST(foo_should_foo); 65 | } 66 | 67 | /* Add definitions that need to be in the test runner's main file. */ 68 | GREATEST_MAIN_DEFS(); 69 | 70 | /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ 71 | int run_tests(void) { 72 | GREATEST_INIT(); /* init. greatest internals */ 73 | /* List of suites to run (if any). */ 74 | RUN_SUITE(suite); 75 | 76 | /* Tests can also be run directly, without using test suites. */ 77 | RUN_TEST(foo_should_foo); 78 | 79 | GREATEST_PRINT_REPORT(); /* display results */ 80 | return greatest_all_passed(); 81 | } 82 | 83 | /* main(), for a standalone command-line test runner. 84 | * This replaces run_tests above, and adds command line option 85 | * handling and exiting with a pass/fail status. */ 86 | int main(int argc, char **argv) { 87 | GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ 88 | RUN_SUITE(suite); 89 | GREATEST_MAIN_END(); /* display results */ 90 | } 91 | 92 | #endif 93 | /*********************************************************************/ 94 | 95 | 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | /*********** 102 | * Options * 103 | ***********/ 104 | 105 | /* Default column width for non-verbose output. */ 106 | #ifndef GREATEST_DEFAULT_WIDTH 107 | #define GREATEST_DEFAULT_WIDTH 72 108 | #endif 109 | 110 | /* FILE *, for test logging. */ 111 | #ifndef GREATEST_STDOUT 112 | #define GREATEST_STDOUT stdout 113 | #endif 114 | 115 | /* Remove GREATEST_ prefix from most commonly used symbols? */ 116 | #ifndef GREATEST_USE_ABBREVS 117 | #define GREATEST_USE_ABBREVS 1 118 | #endif 119 | 120 | /* Set to 0 to disable all use of setjmp/longjmp. */ 121 | #ifndef GREATEST_USE_LONGJMP 122 | #define GREATEST_USE_LONGJMP 1 123 | #endif 124 | 125 | /* Make it possible to replace fprintf with another 126 | * function with the same interface. */ 127 | #ifndef GREATEST_FPRINTF 128 | #define GREATEST_FPRINTF fprintf 129 | #endif 130 | 131 | #if GREATEST_USE_LONGJMP 132 | #include 133 | #endif 134 | 135 | /* Set to 0 to disable all use of time.h / clock(). */ 136 | #ifndef GREATEST_USE_TIME 137 | #define GREATEST_USE_TIME 1 138 | #endif 139 | 140 | #if GREATEST_USE_TIME 141 | #include 142 | #endif 143 | 144 | /* Floating point type, for ASSERT_IN_RANGE. */ 145 | #ifndef GREATEST_FLOAT 146 | #define GREATEST_FLOAT double 147 | #define GREATEST_FLOAT_FMT "%g" 148 | #endif 149 | 150 | /* Size of buffer for test name + optional '_' separator and suffix */ 151 | #ifndef GREATEST_TESTNAME_BUF_SIZE 152 | #define GREATEST_TESTNAME_BUF_SIZE 128 153 | #endif 154 | 155 | 156 | /********* 157 | * Types * 158 | *********/ 159 | 160 | /* Info for the current running suite. */ 161 | typedef struct greatest_suite_info { 162 | unsigned int tests_run; 163 | unsigned int passed; 164 | unsigned int failed; 165 | unsigned int skipped; 166 | 167 | #if GREATEST_USE_TIME 168 | /* timers, pre/post running suite and individual tests */ 169 | clock_t pre_suite; 170 | clock_t post_suite; 171 | clock_t pre_test; 172 | clock_t post_test; 173 | #endif 174 | } greatest_suite_info; 175 | 176 | /* Type for a suite function. */ 177 | typedef void greatest_suite_cb(void); 178 | 179 | /* Types for setup/teardown callbacks. If non-NULL, these will be run 180 | * and passed the pointer to their additional data. */ 181 | typedef void greatest_setup_cb(void *udata); 182 | typedef void greatest_teardown_cb(void *udata); 183 | 184 | /* Type for an equality comparison between two pointers of the same type. 185 | * Should return non-0 if equal, otherwise 0. 186 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 187 | typedef int greatest_equal_cb(const void *expd, const void *got, void *udata); 188 | 189 | /* Type for a callback that prints a value pointed to by T. 190 | * Return value has the same meaning as printf's. 191 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 192 | typedef int greatest_printf_cb(const void *t, void *udata); 193 | 194 | /* Callbacks for an arbitrary type; needed for type-specific 195 | * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ 196 | typedef struct greatest_type_info { 197 | greatest_equal_cb *equal; 198 | greatest_printf_cb *print; 199 | } greatest_type_info; 200 | 201 | typedef struct greatest_memory_cmp_env { 202 | const unsigned char *exp; 203 | const unsigned char *got; 204 | size_t size; 205 | } greatest_memory_cmp_env; 206 | 207 | /* Callbacks for string and raw memory types. */ 208 | extern greatest_type_info greatest_type_info_string; 209 | extern greatest_type_info greatest_type_info_memory; 210 | 211 | typedef enum { 212 | GREATEST_FLAG_FIRST_FAIL = 0x01, 213 | GREATEST_FLAG_LIST_ONLY = 0x02, 214 | GREATEST_FLAG_ABORT_ON_FAIL = 0x04 215 | } greatest_flag_t; 216 | 217 | /* Internal state for a PRNG, used to shuffle test order. */ 218 | struct greatest_prng { 219 | unsigned char random_order; /* use random ordering? */ 220 | unsigned char initialized; /* is random ordering initialized? */ 221 | unsigned char pad_0[6]; 222 | unsigned long state; /* PRNG state */ 223 | unsigned long count; /* how many tests, this pass */ 224 | unsigned long count_ceil; /* total number of tests */ 225 | unsigned long count_run; /* total tests run */ 226 | unsigned long a; /* LCG multiplier */ 227 | unsigned long c; /* LCG increment */ 228 | unsigned long m; /* LCG modulus, based on count_ceil */ 229 | }; 230 | 231 | /* Struct containing all test runner state. */ 232 | typedef struct greatest_run_info { 233 | unsigned char flags; 234 | unsigned char verbosity; 235 | unsigned char pad_0[2]; 236 | 237 | unsigned int tests_run; /* total test count */ 238 | 239 | /* currently running test suite */ 240 | greatest_suite_info suite; 241 | 242 | /* overall pass/fail/skip counts */ 243 | unsigned int passed; 244 | unsigned int failed; 245 | unsigned int skipped; 246 | unsigned int assertions; 247 | 248 | /* info to print about the most recent failure */ 249 | unsigned int fail_line; 250 | unsigned int pad_1; 251 | const char *fail_file; 252 | const char *msg; 253 | 254 | /* current setup/teardown hooks and userdata */ 255 | greatest_setup_cb *setup; 256 | void *setup_udata; 257 | greatest_teardown_cb *teardown; 258 | void *teardown_udata; 259 | 260 | /* formatting info for ".....s...F"-style output */ 261 | unsigned int col; 262 | unsigned int width; 263 | 264 | /* only run a specific suite or test */ 265 | const char *suite_filter; 266 | const char *test_filter; 267 | const char *test_exclude; 268 | const char *name_suffix; /* print suffix with test name */ 269 | char name_buf[GREATEST_TESTNAME_BUF_SIZE]; 270 | 271 | struct greatest_prng prng[2]; /* 0: suites, 1: tests */ 272 | 273 | #if GREATEST_USE_TIME 274 | /* overall timers */ 275 | clock_t begin; 276 | clock_t end; 277 | #endif 278 | 279 | #if GREATEST_USE_LONGJMP 280 | int pad_jmp_buf; 281 | unsigned char pad_2[4]; 282 | jmp_buf jump_dest; 283 | #endif 284 | } greatest_run_info; 285 | 286 | struct greatest_report_t { 287 | /* overall pass/fail/skip counts */ 288 | unsigned int passed; 289 | unsigned int failed; 290 | unsigned int skipped; 291 | unsigned int assertions; 292 | }; 293 | 294 | /* Global var for the current testing context. 295 | * Initialized by GREATEST_MAIN_DEFS(). */ 296 | extern greatest_run_info greatest_info; 297 | 298 | /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ 299 | typedef const char *greatest_enum_str_fun(int value); 300 | 301 | 302 | /********************** 303 | * Exported functions * 304 | **********************/ 305 | 306 | /* These are used internally by greatest macros. */ 307 | int greatest_test_pre(const char *name); 308 | void greatest_test_post(int res); 309 | int greatest_do_assert_equal_t(const void *expd, const void *got, 310 | greatest_type_info *type_info, void *udata); 311 | void greatest_prng_init_first_pass(int id); 312 | int greatest_prng_init_second_pass(int id, unsigned long seed); 313 | void greatest_prng_step(int id); 314 | 315 | /* These are part of the public greatest API. */ 316 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); 317 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); 318 | void GREATEST_INIT(void); 319 | void GREATEST_PRINT_REPORT(void); 320 | int greatest_all_passed(void); 321 | void greatest_set_suite_filter(const char *filter); 322 | void greatest_set_test_filter(const char *filter); 323 | void greatest_set_test_exclude(const char *filter); 324 | void greatest_stop_at_first_fail(void); 325 | void greatest_abort_on_fail(void); 326 | void greatest_list_only(void); 327 | void greatest_get_report(struct greatest_report_t *report); 328 | unsigned int greatest_get_verbosity(void); 329 | void greatest_set_verbosity(unsigned int verbosity); 330 | void greatest_set_flag(greatest_flag_t flag); 331 | void greatest_set_test_suffix(const char *suffix); 332 | 333 | 334 | /******************** 335 | * Language Support * 336 | ********************/ 337 | 338 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing 339 | * without needing to manually manage the argument struct. */ 340 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 19901L) || \ 341 | (defined(_MSC_VER) && _MSC_VER >= 1800) 342 | #define GREATEST_VA_ARGS 343 | #endif 344 | 345 | 346 | /********** 347 | * Macros * 348 | **********/ 349 | 350 | /* Define a suite. (The duplication is intentional -- it eliminates 351 | * a warning from -Wmissing-declarations.) */ 352 | #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) 353 | 354 | /* Declare a suite, provided by another compilation unit. */ 355 | #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) 356 | 357 | /* Start defining a test function. 358 | * The arguments are not included, to allow parametric testing. */ 359 | #define GREATEST_TEST static enum greatest_test_res 360 | 361 | /* PASS/FAIL/SKIP result from a test. Used internally. */ 362 | typedef enum greatest_test_res { 363 | GREATEST_TEST_RES_PASS = 0, 364 | GREATEST_TEST_RES_FAIL = -1, 365 | GREATEST_TEST_RES_SKIP = 1 366 | } greatest_test_res; 367 | 368 | /* Run a suite. */ 369 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) 370 | 371 | /* Run a test in the current suite. */ 372 | #define GREATEST_RUN_TEST(TEST) \ 373 | do { \ 374 | if (greatest_test_pre(#TEST) == 1) { \ 375 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 376 | if (res == GREATEST_TEST_RES_PASS) { \ 377 | res = TEST(); \ 378 | } \ 379 | greatest_test_post(res); \ 380 | } \ 381 | } while (0) 382 | 383 | /* Ignore a test, don't warn about it being unused. */ 384 | #define GREATEST_IGNORE_TEST(TEST) (void)TEST 385 | 386 | /* Run a test in the current suite with one void * argument, 387 | * which can be a pointer to a struct with multiple arguments. */ 388 | #define GREATEST_RUN_TEST1(TEST, ENV) \ 389 | do { \ 390 | if (greatest_test_pre(#TEST) == 1) { \ 391 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 392 | if (res == GREATEST_TEST_RES_PASS) { \ 393 | res = TEST(ENV); \ 394 | } \ 395 | greatest_test_post(res); \ 396 | } \ 397 | } while (0) 398 | 399 | #ifdef GREATEST_VA_ARGS 400 | #define GREATEST_RUN_TESTp(TEST, ...) \ 401 | do { \ 402 | if (greatest_test_pre(#TEST) == 1) { \ 403 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 404 | if (res == GREATEST_TEST_RES_PASS) { \ 405 | res = TEST(__VA_ARGS__); \ 406 | } \ 407 | greatest_test_post(res); \ 408 | } \ 409 | } while (0) 410 | #endif 411 | 412 | 413 | /* Check if the test runner is in verbose mode. */ 414 | #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) 415 | #define GREATEST_LIST_ONLY() \ 416 | (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) 417 | #define GREATEST_FIRST_FAIL() \ 418 | (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) 419 | #define GREATEST_ABORT_ON_FAIL() \ 420 | (greatest_info.flags & GREATEST_FLAG_ABORT_ON_FAIL) 421 | #define GREATEST_FAILURE_ABORT() \ 422 | (GREATEST_FIRST_FAIL() && \ 423 | (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) 424 | 425 | /* Message-less forms of tests defined below. */ 426 | #define GREATEST_PASS() GREATEST_PASSm(NULL) 427 | #define GREATEST_FAIL() GREATEST_FAILm(NULL) 428 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL) 429 | #define GREATEST_ASSERT(COND) \ 430 | GREATEST_ASSERTm(#COND, COND) 431 | #define GREATEST_ASSERT_OR_LONGJMP(COND) \ 432 | GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) 433 | #define GREATEST_ASSERT_FALSE(COND) \ 434 | GREATEST_ASSERT_FALSEm(#COND, COND) 435 | #define GREATEST_ASSERT_EQ(EXP, GOT) \ 436 | GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) 437 | #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ 438 | GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) 439 | #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ 440 | GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) 441 | #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ 442 | GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) 443 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ 444 | GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) 445 | #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ 446 | GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 447 | #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ 448 | GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 449 | #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ 450 | GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) 451 | 452 | /* The following forms take an additional message argument first, 453 | * to be displayed by the test runner. */ 454 | 455 | /* Fail if a condition is not true, with message. */ 456 | #define GREATEST_ASSERTm(MSG, COND) \ 457 | do { \ 458 | greatest_info.assertions++; \ 459 | if (!(COND)) { GREATEST_FAILm(MSG); } \ 460 | } while (0) 461 | 462 | /* Fail if a condition is not true, longjmping out of test. */ 463 | #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ 464 | do { \ 465 | greatest_info.assertions++; \ 466 | if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ 467 | } while (0) 468 | 469 | /* Fail if a condition is not false, with message. */ 470 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \ 471 | do { \ 472 | greatest_info.assertions++; \ 473 | if ((COND)) { GREATEST_FAILm(MSG); } \ 474 | } while (0) 475 | 476 | /* Fail if EXP != GOT (equality comparison by ==). */ 477 | #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ 478 | do { \ 479 | greatest_info.assertions++; \ 480 | if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ 481 | } while (0) 482 | 483 | /* Fail if EXP != GOT (equality comparison by ==). 484 | * Warning: FMT, EXP, and GOT will be evaluated more 485 | * than once on failure. */ 486 | #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ 487 | do { \ 488 | greatest_info.assertions++; \ 489 | if ((EXP) != (GOT)) { \ 490 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 491 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ 492 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 493 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ 494 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 495 | GREATEST_FAILm(MSG); \ 496 | } \ 497 | } while (0) 498 | 499 | /* Fail if EXP is not equal to GOT, printing enum IDs. */ 500 | #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ 501 | do { \ 502 | int greatest_EXP = (int)(EXP); \ 503 | int greatest_GOT = (int)(GOT); \ 504 | greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ 505 | if (greatest_EXP != greatest_GOT) { \ 506 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ 507 | greatest_ENUM_STR(greatest_EXP)); \ 508 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ 509 | greatest_ENUM_STR(greatest_GOT)); \ 510 | GREATEST_FAILm(MSG); \ 511 | } \ 512 | } while (0) \ 513 | 514 | /* Fail if GOT not in range of EXP +|- TOL. */ 515 | #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ 516 | do { \ 517 | GREATEST_FLOAT greatest_EXP = (EXP); \ 518 | GREATEST_FLOAT greatest_GOT = (GOT); \ 519 | GREATEST_FLOAT greatest_TOL = (TOL); \ 520 | greatest_info.assertions++; \ 521 | if ((greatest_EXP > greatest_GOT && \ 522 | greatest_EXP - greatest_GOT > greatest_TOL) || \ 523 | (greatest_EXP < greatest_GOT && \ 524 | greatest_GOT - greatest_EXP > greatest_TOL)) { \ 525 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 526 | "\nExpected: " GREATEST_FLOAT_FMT \ 527 | " +/- " GREATEST_FLOAT_FMT \ 528 | "\n Got: " GREATEST_FLOAT_FMT \ 529 | "\n", \ 530 | greatest_EXP, greatest_TOL, greatest_GOT); \ 531 | GREATEST_FAILm(MSG); \ 532 | } \ 533 | } while (0) 534 | 535 | /* Fail if EXP is not equal to GOT, according to strcmp. */ 536 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ 537 | do { \ 538 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 539 | &greatest_type_info_string, NULL); \ 540 | } while (0) \ 541 | 542 | /* Fail if EXP is not equal to GOT, according to strncmp. */ 543 | #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ 544 | do { \ 545 | size_t size = SIZE; \ 546 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 547 | &greatest_type_info_string, &size); \ 548 | } while (0) \ 549 | 550 | /* Fail if EXP is not equal to GOT, according to memcmp. */ 551 | #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ 552 | do { \ 553 | greatest_memory_cmp_env env; \ 554 | env.exp = (const unsigned char *)EXP; \ 555 | env.got = (const unsigned char *)GOT; \ 556 | env.size = SIZE; \ 557 | GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ 558 | &greatest_type_info_memory, &env); \ 559 | } while (0) \ 560 | 561 | /* Fail if EXP is not equal to GOT, according to a comparison 562 | * callback in TYPE_INFO. If they are not equal, optionally use a 563 | * print callback in TYPE_INFO to print them. */ 564 | #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ 565 | do { \ 566 | greatest_type_info *type_info = (TYPE_INFO); \ 567 | greatest_info.assertions++; \ 568 | if (!greatest_do_assert_equal_t(EXP, GOT, \ 569 | type_info, UDATA)) { \ 570 | if (type_info == NULL || type_info->equal == NULL) { \ 571 | GREATEST_FAILm("type_info->equal callback missing!"); \ 572 | } else { \ 573 | GREATEST_FAILm(MSG); \ 574 | } \ 575 | } \ 576 | } while (0) \ 577 | 578 | /* Pass. */ 579 | #define GREATEST_PASSm(MSG) \ 580 | do { \ 581 | greatest_info.msg = MSG; \ 582 | return GREATEST_TEST_RES_PASS; \ 583 | } while (0) 584 | 585 | /* Fail. */ 586 | #define GREATEST_FAILm(MSG) \ 587 | do { \ 588 | greatest_info.fail_file = __FILE__; \ 589 | greatest_info.fail_line = __LINE__; \ 590 | greatest_info.msg = MSG; \ 591 | if (GREATEST_ABORT_ON_FAIL()) { abort(); } \ 592 | return GREATEST_TEST_RES_FAIL; \ 593 | } while (0) 594 | 595 | /* Optional GREATEST_FAILm variant that longjmps. */ 596 | #if GREATEST_USE_LONGJMP 597 | #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) 598 | #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ 599 | do { \ 600 | greatest_info.fail_file = __FILE__; \ 601 | greatest_info.fail_line = __LINE__; \ 602 | greatest_info.msg = MSG; \ 603 | longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ 604 | } while (0) 605 | #endif 606 | 607 | /* Skip the current test. */ 608 | #define GREATEST_SKIPm(MSG) \ 609 | do { \ 610 | greatest_info.msg = MSG; \ 611 | return GREATEST_TEST_RES_SKIP; \ 612 | } while (0) 613 | 614 | /* Check the result of a subfunction using ASSERT, etc. */ 615 | #define GREATEST_CHECK_CALL(RES) \ 616 | do { \ 617 | enum greatest_test_res greatest_RES = RES; \ 618 | if (greatest_RES != GREATEST_TEST_RES_PASS) { \ 619 | return greatest_RES; \ 620 | } \ 621 | } while (0) \ 622 | 623 | #if GREATEST_USE_TIME 624 | #define GREATEST_SET_TIME(NAME) \ 625 | NAME = clock(); \ 626 | if (NAME == (clock_t) -1) { \ 627 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 628 | "clock error: %s\n", #NAME); \ 629 | exit(EXIT_FAILURE); \ 630 | } 631 | 632 | #define GREATEST_CLOCK_DIFF(C1, C2) \ 633 | GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ 634 | (long unsigned int) (C2) - (long unsigned int)(C1), \ 635 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) 636 | #else 637 | #define GREATEST_SET_TIME(UNUSED) 638 | #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) 639 | #endif 640 | 641 | #if GREATEST_USE_LONGJMP 642 | #define GREATEST_SAVE_CONTEXT() \ 643 | /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ 644 | * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ 645 | ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) 646 | #else 647 | #define GREATEST_SAVE_CONTEXT() \ 648 | /*a no-op, since setjmp/longjmp aren't being used */ \ 649 | GREATEST_TEST_RES_PASS 650 | #endif 651 | 652 | /* Run every suite / test function run within BODY in pseudo-random 653 | * order, seeded by SEED. (The top 3 bits of the seed are ignored.) 654 | * 655 | * This should be called like: 656 | * GREATEST_SHUFFLE_TESTS(seed, { 657 | * GREATEST_RUN_TEST(some_test); 658 | * GREATEST_RUN_TEST(some_other_test); 659 | * GREATEST_RUN_TEST(yet_another_test); 660 | * }); 661 | * 662 | * Note that the body of the second argument will be evaluated 663 | * multiple times. */ 664 | #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) 665 | #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) 666 | #define GREATEST_SHUFFLE(ID, SD, BODY) \ 667 | do { \ 668 | struct greatest_prng *prng = &greatest_info.prng[ID]; \ 669 | greatest_prng_init_first_pass(ID); \ 670 | do { \ 671 | prng->count = 0; \ 672 | if (prng->initialized) { greatest_prng_step(ID); } \ 673 | BODY; \ 674 | if (!prng->initialized) { \ 675 | if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ 676 | } else if (prng->count_run == prng->count_ceil) { \ 677 | break; \ 678 | } \ 679 | } while (!GREATEST_FAILURE_ABORT()); \ 680 | prng->count_run = prng->random_order = prng->initialized = 0; \ 681 | } while(0) 682 | 683 | /* Include several function definitions in the main test file. */ 684 | #define GREATEST_MAIN_DEFS() \ 685 | \ 686 | /* Is FILTER a subset of NAME? */ \ 687 | static int greatest_name_match(const char *name, const char *filter, \ 688 | int res_if_none) { \ 689 | size_t offset = 0; \ 690 | size_t filter_len = filter ? strlen(filter) : 0; \ 691 | if (filter_len == 0) { return res_if_none; } /* no filter */ \ 692 | while (name[offset] != '\0') { \ 693 | if (name[offset] == filter[0]) { \ 694 | if (0 == strncmp(&name[offset], filter, filter_len)) { \ 695 | return 1; \ 696 | } \ 697 | } \ 698 | offset++; \ 699 | } \ 700 | \ 701 | return 0; \ 702 | } \ 703 | \ 704 | static void greatest_buffer_test_name(const char *name) { \ 705 | struct greatest_run_info *g = &greatest_info; \ 706 | size_t len = strlen(name), size = sizeof(g->name_buf); \ 707 | memset(g->name_buf, 0x00, size); \ 708 | (void)strncat(g->name_buf, name, size - 1); \ 709 | if (g->name_suffix && (len + 1 < size)) { \ 710 | g->name_buf[len] = '_'; \ 711 | strncat(&g->name_buf[len+1], g->name_suffix, size-(len+2)); \ 712 | } \ 713 | } \ 714 | \ 715 | /* Before running a test, check the name filtering and \ 716 | * test shuffling state, if applicable, and then call setup hooks. */ \ 717 | int greatest_test_pre(const char *name) { \ 718 | struct greatest_run_info *g = &greatest_info; \ 719 | int match; \ 720 | greatest_buffer_test_name(name); \ 721 | match = greatest_name_match(g->name_buf, g->test_filter, 1) && \ 722 | !greatest_name_match(g->name_buf, g->test_exclude, 0); \ 723 | if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ 724 | if (match) { \ 725 | GREATEST_FPRINTF(GREATEST_STDOUT, " %s\n", g->name_buf); \ 726 | } \ 727 | goto clear; \ 728 | } \ 729 | if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ 730 | struct greatest_prng *p = &g->prng[1]; \ 731 | if (p->random_order) { \ 732 | p->count++; \ 733 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 734 | goto clear; /* don't run this test yet */ \ 735 | } \ 736 | } \ 737 | GREATEST_SET_TIME(g->suite.pre_test); \ 738 | if (g->setup) { g->setup(g->setup_udata); } \ 739 | p->count_run++; \ 740 | return 1; /* test should be run */ \ 741 | } else { \ 742 | goto clear; /* skipped */ \ 743 | } \ 744 | clear: \ 745 | g->name_suffix = NULL; \ 746 | return 0; \ 747 | } \ 748 | \ 749 | static void greatest_do_pass(void) { \ 750 | struct greatest_run_info *g = &greatest_info; \ 751 | if (GREATEST_IS_VERBOSE()) { \ 752 | GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ 753 | g->name_buf, g->msg ? g->msg : ""); \ 754 | } else { \ 755 | GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ 756 | } \ 757 | g->suite.passed++; \ 758 | } \ 759 | \ 760 | static void greatest_do_fail(void) { \ 761 | struct greatest_run_info *g = &greatest_info; \ 762 | if (GREATEST_IS_VERBOSE()) { \ 763 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 764 | "FAIL %s: %s (%s:%u)", g->name_buf, \ 765 | g->msg ? g->msg : "", g->fail_file, g->fail_line); \ 766 | } else { \ 767 | GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ 768 | g->col++; /* add linebreak if in line of '.'s */ \ 769 | if (g->col != 0) { \ 770 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 771 | g->col = 0; \ 772 | } \ 773 | GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ 774 | g->name_buf, g->msg ? g->msg : "", \ 775 | g->fail_file, g->fail_line); \ 776 | } \ 777 | g->suite.failed++; \ 778 | } \ 779 | \ 780 | static void greatest_do_skip(void) { \ 781 | struct greatest_run_info *g = &greatest_info; \ 782 | if (GREATEST_IS_VERBOSE()) { \ 783 | GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ 784 | g->name_buf, g->msg ? g->msg : ""); \ 785 | } else { \ 786 | GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ 787 | } \ 788 | g->suite.skipped++; \ 789 | } \ 790 | \ 791 | void greatest_test_post(int res) { \ 792 | GREATEST_SET_TIME(greatest_info.suite.post_test); \ 793 | if (greatest_info.teardown) { \ 794 | void *udata = greatest_info.teardown_udata; \ 795 | greatest_info.teardown(udata); \ 796 | } \ 797 | \ 798 | if (res <= GREATEST_TEST_RES_FAIL) { \ 799 | greatest_do_fail(); \ 800 | } else if (res >= GREATEST_TEST_RES_SKIP) { \ 801 | greatest_do_skip(); \ 802 | } else if (res == GREATEST_TEST_RES_PASS) { \ 803 | greatest_do_pass(); \ 804 | } \ 805 | greatest_info.name_suffix = NULL; \ 806 | greatest_info.suite.tests_run++; \ 807 | greatest_info.col++; \ 808 | if (GREATEST_IS_VERBOSE()) { \ 809 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ 810 | greatest_info.suite.post_test); \ 811 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 812 | } else if (greatest_info.col % greatest_info.width == 0) { \ 813 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 814 | greatest_info.col = 0; \ 815 | } \ 816 | fflush(GREATEST_STDOUT); \ 817 | } \ 818 | \ 819 | static void report_suite(void) { \ 820 | if (greatest_info.suite.tests_run > 0) { \ 821 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 822 | "\n%u test%s - %u passed, %u failed, %u skipped", \ 823 | greatest_info.suite.tests_run, \ 824 | greatest_info.suite.tests_run == 1 ? "" : "s", \ 825 | greatest_info.suite.passed, \ 826 | greatest_info.suite.failed, \ 827 | greatest_info.suite.skipped); \ 828 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ 829 | greatest_info.suite.post_suite); \ 830 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 831 | } \ 832 | } \ 833 | \ 834 | static void update_counts_and_reset_suite(void) { \ 835 | greatest_info.setup = NULL; \ 836 | greatest_info.setup_udata = NULL; \ 837 | greatest_info.teardown = NULL; \ 838 | greatest_info.teardown_udata = NULL; \ 839 | greatest_info.passed += greatest_info.suite.passed; \ 840 | greatest_info.failed += greatest_info.suite.failed; \ 841 | greatest_info.skipped += greatest_info.suite.skipped; \ 842 | greatest_info.tests_run += greatest_info.suite.tests_run; \ 843 | memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ 844 | greatest_info.col = 0; \ 845 | } \ 846 | \ 847 | static int greatest_suite_pre(const char *suite_name) { \ 848 | struct greatest_prng *p = &greatest_info.prng[0]; \ 849 | if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ 850 | || (GREATEST_FAILURE_ABORT())) { return 0; } \ 851 | if (p->random_order) { \ 852 | p->count++; \ 853 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 854 | return 0; /* don't run this suite yet */ \ 855 | } \ 856 | } \ 857 | p->count_run++; \ 858 | update_counts_and_reset_suite(); \ 859 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ 860 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ 861 | return 1; \ 862 | } \ 863 | \ 864 | static void greatest_suite_post(void) { \ 865 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \ 866 | report_suite(); \ 867 | } \ 868 | \ 869 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \ 870 | const char *suite_name) { \ 871 | if (greatest_suite_pre(suite_name)) { \ 872 | suite_cb(); \ 873 | greatest_suite_post(); \ 874 | } \ 875 | } \ 876 | \ 877 | int greatest_do_assert_equal_t(const void *expd, const void *got, \ 878 | greatest_type_info *type_info, void *udata) { \ 879 | int eq = 0; \ 880 | if (type_info == NULL || type_info->equal == NULL) { \ 881 | return 0; \ 882 | } \ 883 | eq = type_info->equal(expd, got, udata); \ 884 | if (!eq) { \ 885 | if (type_info->print != NULL) { \ 886 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 887 | (void)type_info->print(expd, udata); \ 888 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 889 | (void)type_info->print(got, udata); \ 890 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 891 | } \ 892 | } \ 893 | return eq; \ 894 | } \ 895 | \ 896 | static void greatest_usage(const char *name) { \ 897 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 898 | "Usage: %s [-hlfav] [-s SUITE] [-t TEST] [-x EXCLUDE]\n" \ 899 | " -h, --help print this Help\n" \ 900 | " -l List suites and tests, then exit (dry run)\n" \ 901 | " -f Stop runner after first failure\n" \ 902 | " -a Abort on first failure (implies -f)\n" \ 903 | " -v Verbose output\n" \ 904 | " -s SUITE only run suites containing substring SUITE\n" \ 905 | " -t TEST only run tests containing substring TEST\n" \ 906 | " -x EXCLUDE exclude tests containing substring EXCLUDE\n", \ 907 | name); \ 908 | } \ 909 | \ 910 | static void greatest_parse_options(int argc, char **argv) { \ 911 | int i = 0; \ 912 | for (i = 1; i < argc; i++) { \ 913 | if (argv[i][0] == '-') { \ 914 | char f = argv[i][1]; \ 915 | if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ 916 | greatest_usage(argv[0]); exit(EXIT_FAILURE); \ 917 | } \ 918 | switch (f) { \ 919 | case 's': /* suite name filter */ \ 920 | greatest_set_suite_filter(argv[i + 1]); i++; break; \ 921 | case 't': /* test name filter */ \ 922 | greatest_set_test_filter(argv[i + 1]); i++; break; \ 923 | case 'x': /* test name exclusion */ \ 924 | greatest_set_test_exclude(argv[i + 1]); i++; break; \ 925 | case 'f': /* first fail flag */ \ 926 | greatest_stop_at_first_fail(); break; \ 927 | case 'a': /* abort() on fail flag */ \ 928 | greatest_abort_on_fail(); break; \ 929 | case 'l': /* list only (dry run) */ \ 930 | greatest_list_only(); break; \ 931 | case 'v': /* first fail flag */ \ 932 | greatest_info.verbosity++; break; \ 933 | case 'h': /* help */ \ 934 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 935 | case '-': \ 936 | if (0 == strncmp("--help", argv[i], 6)) { \ 937 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 938 | } else if (0 == strncmp("--", argv[i], 2)) { \ 939 | return; /* ignore following arguments */ \ 940 | } /* fall through */ \ 941 | default: \ 942 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 943 | "Unknown argument '%s'\n", argv[i]); \ 944 | greatest_usage(argv[0]); \ 945 | exit(EXIT_FAILURE); \ 946 | } \ 947 | } \ 948 | } \ 949 | } \ 950 | \ 951 | int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ 952 | \ 953 | void greatest_set_test_filter(const char *filter) { \ 954 | greatest_info.test_filter = filter; \ 955 | } \ 956 | \ 957 | void greatest_set_test_exclude(const char *filter) { \ 958 | greatest_info.test_exclude = filter; \ 959 | } \ 960 | \ 961 | void greatest_set_suite_filter(const char *filter) { \ 962 | greatest_info.suite_filter = filter; \ 963 | } \ 964 | \ 965 | void greatest_stop_at_first_fail(void) { \ 966 | greatest_set_flag(GREATEST_FLAG_FIRST_FAIL); \ 967 | } \ 968 | \ 969 | void greatest_abort_on_fail(void) { \ 970 | greatest_set_flag(GREATEST_FLAG_ABORT_ON_FAIL); \ 971 | } \ 972 | \ 973 | void greatest_list_only(void) { \ 974 | greatest_set_flag(GREATEST_FLAG_LIST_ONLY); \ 975 | } \ 976 | \ 977 | void greatest_get_report(struct greatest_report_t *report) { \ 978 | if (report) { \ 979 | report->passed = greatest_info.passed; \ 980 | report->failed = greatest_info.failed; \ 981 | report->skipped = greatest_info.skipped; \ 982 | report->assertions = greatest_info.assertions; \ 983 | } \ 984 | } \ 985 | \ 986 | unsigned int greatest_get_verbosity(void) { \ 987 | return greatest_info.verbosity; \ 988 | } \ 989 | \ 990 | void greatest_set_verbosity(unsigned int verbosity) { \ 991 | greatest_info.verbosity = (unsigned char)verbosity; \ 992 | } \ 993 | \ 994 | void greatest_set_flag(greatest_flag_t flag) { \ 995 | greatest_info.flags = (unsigned char)(greatest_info.flags | flag); \ 996 | } \ 997 | \ 998 | void greatest_set_test_suffix(const char *suffix) { \ 999 | greatest_info.name_suffix = suffix; \ 1000 | } \ 1001 | \ 1002 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ 1003 | greatest_info.setup = cb; \ 1004 | greatest_info.setup_udata = udata; \ 1005 | } \ 1006 | \ 1007 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ 1008 | void *udata) { \ 1009 | greatest_info.teardown = cb; \ 1010 | greatest_info.teardown_udata = udata; \ 1011 | } \ 1012 | \ 1013 | static int greatest_string_equal_cb(const void *expd, const void *got, \ 1014 | void *udata) { \ 1015 | size_t *size = (size_t *)udata; \ 1016 | return (size != NULL \ 1017 | ? (0 == strncmp((const char *)expd, (const char *)got, *size)) \ 1018 | : (0 == strcmp((const char *)expd, (const char *)got))); \ 1019 | } \ 1020 | \ 1021 | static int greatest_string_printf_cb(const void *t, void *udata) { \ 1022 | (void)udata; /* note: does not check \0 termination. */ \ 1023 | return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ 1024 | } \ 1025 | \ 1026 | greatest_type_info greatest_type_info_string = { \ 1027 | greatest_string_equal_cb, \ 1028 | greatest_string_printf_cb, \ 1029 | }; \ 1030 | \ 1031 | static int greatest_memory_equal_cb(const void *expd, const void *got, \ 1032 | void *udata) { \ 1033 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 1034 | return (0 == memcmp(expd, got, env->size)); \ 1035 | } \ 1036 | \ 1037 | /* Hexdump raw memory, with differences highlighted */ \ 1038 | static int greatest_memory_printf_cb(const void *t, void *udata) { \ 1039 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 1040 | const unsigned char *buf = (const unsigned char *)t; \ 1041 | unsigned char diff_mark = ' '; \ 1042 | FILE *out = GREATEST_STDOUT; \ 1043 | size_t i, line_i, line_len = 0; \ 1044 | int len = 0; /* format hexdump with differences highlighted */ \ 1045 | for (i = 0; i < env->size; i+= line_len) { \ 1046 | diff_mark = ' '; \ 1047 | line_len = env->size - i; \ 1048 | if (line_len > 16) { line_len = 16; } \ 1049 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1050 | if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ 1051 | } \ 1052 | len += GREATEST_FPRINTF(out, "\n%04x %c ", \ 1053 | (unsigned int)i, diff_mark); \ 1054 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1055 | int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ 1056 | len += GREATEST_FPRINTF(out, "%02x%c", \ 1057 | buf[line_i], m ? ' ' : '<'); \ 1058 | } \ 1059 | for (line_i = 0; line_i < 16 - line_len; line_i++) { \ 1060 | len += GREATEST_FPRINTF(out, " "); \ 1061 | } \ 1062 | GREATEST_FPRINTF(out, " "); \ 1063 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1064 | unsigned char c = buf[line_i]; \ 1065 | len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ 1066 | } \ 1067 | } \ 1068 | len += GREATEST_FPRINTF(out, "\n"); \ 1069 | return len; \ 1070 | } \ 1071 | \ 1072 | void greatest_prng_init_first_pass(int id) { \ 1073 | greatest_info.prng[id].random_order = 1; \ 1074 | greatest_info.prng[id].count_run = 0; \ 1075 | } \ 1076 | \ 1077 | int greatest_prng_init_second_pass(int id, unsigned long seed) { \ 1078 | struct greatest_prng *p = &greatest_info.prng[id]; \ 1079 | if (p->count == 0) { return 0; } \ 1080 | p->count_ceil = p->count; \ 1081 | for (p->m = 1; p->m < p->count; p->m <<= 1) {} \ 1082 | p->state = seed & 0x1fffffff; /* only use lower 29 bits */ \ 1083 | p->a = 4LU * p->state; /* to avoid overflow when */ \ 1084 | p->a = (p->a ? p->a : 4) | 1; /* multiplied by 4 */ \ 1085 | p->c = 2147483647; /* and so p->c ((2 ** 31) - 1) is */ \ 1086 | p->initialized = 1; /* always relatively prime to p->a. */ \ 1087 | fprintf(stderr, "init_second_pass: a %lu, c %lu, state %lu\n", \ 1088 | p->a, p->c, p->state); \ 1089 | return 1; \ 1090 | } \ 1091 | \ 1092 | /* Step the pseudorandom number generator until its state reaches \ 1093 | * another test ID between 0 and the test count. \ 1094 | * This use a linear congruential pseudorandom number generator, \ 1095 | * with the power-of-two ceiling of the test count as the modulus, the \ 1096 | * masked seed as the multiplier, and a prime as the increment. For \ 1097 | * each generated value < the test count, run the corresponding test. \ 1098 | * This will visit all IDs 0 <= X < mod once before repeating, \ 1099 | * with a starting position chosen based on the initial seed. \ 1100 | * For details, see: Knuth, The Art of Computer Programming \ 1101 | * Volume. 2, section 3.2.1. */ \ 1102 | void greatest_prng_step(int id) { \ 1103 | struct greatest_prng *p = &greatest_info.prng[id]; \ 1104 | do { \ 1105 | p->state = ((p->a * p->state) + p->c) & (p->m - 1); \ 1106 | } while (p->state >= p->count_ceil); \ 1107 | } \ 1108 | \ 1109 | void GREATEST_INIT(void) { \ 1110 | /* Suppress unused function warning if features aren't used */ \ 1111 | (void)greatest_run_suite; \ 1112 | (void)greatest_parse_options; \ 1113 | (void)greatest_prng_step; \ 1114 | (void)greatest_prng_init_first_pass; \ 1115 | (void)greatest_prng_init_second_pass; \ 1116 | (void)greatest_set_test_suffix; \ 1117 | \ 1118 | memset(&greatest_info, 0, sizeof(greatest_info)); \ 1119 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \ 1120 | GREATEST_SET_TIME(greatest_info.begin); \ 1121 | } \ 1122 | \ 1123 | /* Report passes, failures, skipped tests, the number of \ 1124 | * assertions, and the overall run time. */ \ 1125 | void GREATEST_PRINT_REPORT(void) { \ 1126 | if (!GREATEST_LIST_ONLY()) { \ 1127 | update_counts_and_reset_suite(); \ 1128 | GREATEST_SET_TIME(greatest_info.end); \ 1129 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1130 | "\nTotal: %u test%s", \ 1131 | greatest_info.tests_run, \ 1132 | greatest_info.tests_run == 1 ? "" : "s"); \ 1133 | GREATEST_CLOCK_DIFF(greatest_info.begin, \ 1134 | greatest_info.end); \ 1135 | GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ 1136 | greatest_info.assertions, \ 1137 | greatest_info.assertions == 1 ? "" : "s"); \ 1138 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1139 | "Pass: %u, fail: %u, skip: %u.\n", \ 1140 | greatest_info.passed, \ 1141 | greatest_info.failed, greatest_info.skipped); \ 1142 | } \ 1143 | } \ 1144 | \ 1145 | greatest_type_info greatest_type_info_memory = { \ 1146 | greatest_memory_equal_cb, \ 1147 | greatest_memory_printf_cb, \ 1148 | }; \ 1149 | \ 1150 | greatest_run_info greatest_info 1151 | 1152 | /* Handle command-line arguments, etc. */ 1153 | #define GREATEST_MAIN_BEGIN() \ 1154 | do { \ 1155 | GREATEST_INIT(); \ 1156 | greatest_parse_options(argc, argv); \ 1157 | } while (0) 1158 | 1159 | /* Report results, exit with exit status based on results. */ 1160 | #define GREATEST_MAIN_END() \ 1161 | do { \ 1162 | GREATEST_PRINT_REPORT(); \ 1163 | return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ 1164 | } while (0) 1165 | 1166 | /* Make abbreviations without the GREATEST_ prefix for the 1167 | * most commonly used symbols. */ 1168 | #if GREATEST_USE_ABBREVS 1169 | #define TEST GREATEST_TEST 1170 | #define SUITE GREATEST_SUITE 1171 | #define SUITE_EXTERN GREATEST_SUITE_EXTERN 1172 | #define RUN_TEST GREATEST_RUN_TEST 1173 | #define RUN_TEST1 GREATEST_RUN_TEST1 1174 | #define RUN_SUITE GREATEST_RUN_SUITE 1175 | #define IGNORE_TEST GREATEST_IGNORE_TEST 1176 | #define ASSERT GREATEST_ASSERT 1177 | #define ASSERTm GREATEST_ASSERTm 1178 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE 1179 | #define ASSERT_EQ GREATEST_ASSERT_EQ 1180 | #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT 1181 | #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE 1182 | #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T 1183 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ 1184 | #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ 1185 | #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ 1186 | #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ 1187 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm 1188 | #define ASSERT_EQm GREATEST_ASSERT_EQm 1189 | #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm 1190 | #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm 1191 | #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm 1192 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm 1193 | #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm 1194 | #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm 1195 | #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm 1196 | #define PASS GREATEST_PASS 1197 | #define FAIL GREATEST_FAIL 1198 | #define SKIP GREATEST_SKIP 1199 | #define PASSm GREATEST_PASSm 1200 | #define FAILm GREATEST_FAILm 1201 | #define SKIPm GREATEST_SKIPm 1202 | #define SET_SETUP GREATEST_SET_SETUP_CB 1203 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB 1204 | #define CHECK_CALL GREATEST_CHECK_CALL 1205 | #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS 1206 | #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES 1207 | 1208 | #ifdef GREATEST_VA_ARGS 1209 | #define RUN_TESTp GREATEST_RUN_TESTp 1210 | #endif 1211 | 1212 | #if GREATEST_USE_LONGJMP 1213 | #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP 1214 | #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm 1215 | #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP 1216 | #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm 1217 | #endif 1218 | 1219 | #endif /* USE_ABBREVS */ 1220 | 1221 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 1222 | } 1223 | #endif 1224 | 1225 | #endif 1226 | -------------------------------------------------------------------------------- /imgs/benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonliang-dev/entity-component-system/e65c4cd9d3497f630e45d442eb676a2a656b5b6b/imgs/benchmark.png -------------------------------------------------------------------------------- /imgs/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonliang-dev/entity-component-system/e65c4cd9d3497f630e45d442eb676a2a656b5b6b/imgs/graph.png -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "ecs.h" 2 | #include "greatest.h" 3 | 4 | #include 5 | 6 | TEST map_empty() { 7 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 8 | ecs_map_free(map); 9 | PASS(); 10 | } 11 | 12 | TEST map_set() { 13 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 14 | int x = 10; 15 | ecs_map_set(map, (void *)1, &x); 16 | ecs_map_free(map); 17 | PASS(); 18 | } 19 | 20 | TEST map_get() { 21 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 22 | int x = 10; 23 | ecs_map_set(map, (void *)1, &x); 24 | int *val = ecs_map_get(map, (void *)1); 25 | ASSERT(val != NULL); 26 | ASSERT_EQ(*val, 10); 27 | ecs_map_free(map); 28 | PASS(); 29 | } 30 | 31 | TEST map_set_multiple() { 32 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 33 | ecs_map_set(map, (void *)1, &(int){10}); 34 | ecs_map_set(map, (void *)2, &(int){20}); 35 | ASSERT_EQ(*(int *)ecs_map_get(map, (void *)1), 10); 36 | ASSERT_EQ(*(int *)ecs_map_get(map, (void *)2), 20); 37 | ecs_map_free(map); 38 | PASS(); 39 | } 40 | 41 | TEST map_update() { 42 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 43 | ecs_map_set(map, (void *)1, &(int){10}); 44 | ecs_map_set(map, (void *)1, &(int){100}); 45 | ASSERT_EQ(*(int *)ecs_map_get(map, (void *)1), 100); 46 | ecs_map_free(map); 47 | PASS(); 48 | } 49 | 50 | TEST map_remove() { 51 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 52 | ecs_map_set(map, (void *)1, &(int){10}); 53 | ecs_map_remove(map, (void *)1); 54 | ASSERT_EQ_FMT(NULL, ecs_map_get(map, (void *)1), "%p"); 55 | ecs_map_free(map); 56 | PASS(); 57 | } 58 | 59 | TEST map_set_multiple_and_remove() { 60 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 61 | ecs_map_set(map, (void *)1, &(int){10}); 62 | ecs_map_set(map, (void *)2, &(int){20}); 63 | ecs_map_set(map, (void *)3, &(int){30}); 64 | ecs_map_remove(map, (void *)3); 65 | ASSERT_EQ(*(int *)ecs_map_get(map, (void *)1), 10); 66 | ASSERT_EQ(ecs_map_get(map, (void *)3), NULL); 67 | ecs_map_free(map); 68 | PASS(); 69 | } 70 | 71 | TEST map_set_a_lot(const int count) { 72 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 73 | 74 | for (int i = 1; i < count; i++) { 75 | ecs_map_set(map, (void *)(uintptr_t)i, &(int){i * 10}); 76 | } 77 | 78 | for (int i = 1; i < count; i++) { 79 | ASSERT_EQ(*(int *)ecs_map_get(map, (void *)(uintptr_t)i), i * 10); 80 | } 81 | 82 | ecs_map_free(map); 83 | PASS(); 84 | } 85 | 86 | TEST map_remove_a_lot(int count) { 87 | ecs_map_t *map = ECS_MAP(intptr, int, int, 16); 88 | 89 | for (int i = 1; i < count; i++) { 90 | ecs_map_set(map, (void *)(uintptr_t)i, &(int){i * 10}); 91 | } 92 | 93 | for (int i = 1; i + 1 < count / 2; i += 2) { 94 | ecs_map_remove(map, (void *)(uintptr_t)i); 95 | } 96 | 97 | for (int i = 1; i + 1 < count / 2; i += 2) { 98 | ASSERT_EQ_FMT(NULL, ecs_map_get(map, (void *)(uintptr_t)i), "%p"); 99 | ASSERT_EQ(*(int *)ecs_map_get(map, (void *)(uintptr_t)(i + 1)), 100 | (i + 1) * 10); 101 | } 102 | 103 | ecs_map_free(map); 104 | PASS(); 105 | } 106 | 107 | TEST map_string_keys() { 108 | ecs_map_t *map = ECS_MAP(string, char *, int, 16); 109 | ecs_map_set(map, "foo", &(int){10}); 110 | ecs_map_set(map, "bar", &(int){20}); 111 | ASSERT_EQ(*(int *)ecs_map_get(map, "foo"), 10); 112 | ASSERT_EQ(*(int *)ecs_map_get(map, "bar"), 20); 113 | ASSERT_EQ(NULL, ecs_map_get(map, "baz")); 114 | char *bar = alloca(4); 115 | memcpy(bar, "bar", 4); 116 | ecs_map_remove(map, bar); 117 | ASSERT_EQ(NULL, ecs_map_get(map, "bar")); 118 | ecs_map_free(map); 119 | PASS(); 120 | } 121 | 122 | TEST map_string_keys_struct_values() { 123 | struct person_t { 124 | char *name; 125 | int age; 126 | char *hobby; 127 | }; 128 | 129 | ecs_map_t *map = ECS_MAP(string, char *, struct person_t, 16); 130 | ecs_map_set(map, "jason", &(struct person_t){"Jason", 20, "Playing guitar"}); 131 | ecs_map_set(map, "june", 132 | &(struct person_t){"June", 24, "Listening to music"}); 133 | struct person_t *jason = ecs_map_get(map, "jason"); 134 | struct person_t *june = ecs_map_get(map, "june"); 135 | struct person_t *unknown = ecs_map_get(map, "foobarbaz"); 136 | ASSERT_STR_EQ(jason->name, "Jason"); 137 | ASSERT_EQ(jason->age, 20); 138 | ASSERT_STR_EQ(jason->hobby, "Playing guitar"); 139 | ASSERT_STR_EQ(june->name, "June"); 140 | ASSERT_EQ(june->age, 24); 141 | ASSERT_STR_EQ(june->hobby, "Listening to music"); 142 | ASSERT_EQ(unknown, NULL); 143 | ecs_map_free(map); 144 | PASS(); 145 | } 146 | 147 | SUITE(map) { 148 | RUN_TEST(map_empty); 149 | RUN_TEST(map_set); 150 | RUN_TEST(map_get); 151 | RUN_TEST(map_set_multiple); 152 | RUN_TEST(map_update); 153 | RUN_TEST(map_remove); 154 | RUN_TEST(map_set_multiple_and_remove); 155 | for (int i = 10; i <= 100000; i *= 10) { 156 | RUN_TEST1(map_set_a_lot, i); 157 | RUN_TEST1(map_remove_a_lot, i); 158 | } 159 | RUN_TEST(map_string_keys); 160 | RUN_TEST(map_string_keys_struct_values); 161 | } 162 | 163 | TEST type_empty() { 164 | ecs_type_t *type = ecs_type_new(8); 165 | ecs_type_free(type); 166 | PASS(); 167 | } 168 | 169 | TEST type_contains() { 170 | ecs_type_t *type = ecs_type_new(8); 171 | ASSERT_EQ(ecs_type_index_of(type, 1), -1); 172 | ecs_type_free(type); 173 | PASS(); 174 | } 175 | 176 | TEST type_add_1() { 177 | ecs_type_t *type = ecs_type_new(8); 178 | ecs_type_add(type, 1); 179 | ASSERT_EQ(ecs_type_index_of(type, 1), 0); 180 | ecs_type_free(type); 181 | PASS(); 182 | } 183 | 184 | TEST type_add_multiple(ecs_entity_t count) { 185 | ecs_type_t *type = ecs_type_new(16); 186 | for (ecs_entity_t i = 0; i < count; i++) { 187 | ecs_type_add(type, i + 1); 188 | } 189 | 190 | for (ecs_entity_t i = 0; i < count; i++) { 191 | ASSERT_EQ(ecs_type_index_of(type, i + 1), (int)i); 192 | } 193 | ASSERT_EQ(ecs_type_index_of(type, 0), -1); 194 | ecs_type_free(type); 195 | PASS(); 196 | } 197 | 198 | TEST type_add_multiple_reversed(ecs_entity_t count) { 199 | ecs_type_t *type = ecs_type_new(16); 200 | for (ecs_entity_t i = 0; i < count; i++) { 201 | ecs_type_add(type, count - i); 202 | } 203 | 204 | for (ecs_entity_t i = 0; i < count; i++) { 205 | ASSERT(ecs_type_index_of(type, count - i) != -1); 206 | } 207 | ASSERT_EQ(ecs_type_index_of(type, 0), -1); 208 | ecs_type_free(type); 209 | PASS(); 210 | } 211 | 212 | TEST type_add_multiple_random(ecs_entity_t max) { 213 | ecs_type_t *type = ecs_type_new(16); 214 | ecs_entity_t ran = rand() % max; 215 | for (ecs_entity_t i = 0; i < ran; i++) { 216 | ecs_type_add(type, rand()); 217 | } 218 | 219 | ecs_type_free(type); 220 | PASS(); 221 | } 222 | 223 | TEST type_add_duplicate() { 224 | ecs_type_t *type = ecs_type_new(8); 225 | ecs_type_add(type, 1); 226 | ecs_type_add(type, 1); 227 | ASSERT_EQ(ecs_type_index_of(type, 1), 0); 228 | ecs_type_free(type); 229 | PASS(); 230 | } 231 | 232 | TEST type_remove_from_empty() { 233 | ecs_type_t *type = ecs_type_new(8); 234 | ecs_type_remove(type, 1); 235 | ASSERT_EQ(ecs_type_index_of(type, 1), -1); 236 | ecs_type_free(type); 237 | PASS(); 238 | } 239 | 240 | TEST type_remove_from_1() { 241 | ecs_type_t *type = ecs_type_new(8); 242 | ecs_type_add(type, 1); 243 | ecs_type_remove(type, 1); 244 | ASSERT_EQ(ecs_type_index_of(type, 1), -1); 245 | ecs_type_free(type); 246 | PASS(); 247 | } 248 | 249 | TEST type_remove_from_many() { 250 | ecs_type_t *type = ecs_type_new(8); 251 | ecs_type_add(type, 3); 252 | ecs_type_add(type, 2); 253 | ecs_type_add(type, 5); 254 | ecs_type_remove(type, 2); 255 | ecs_type_add(type, 1); 256 | ASSERT_EQ(ecs_type_index_of(type, 2), -1); 257 | ASSERT(ecs_type_index_of(type, 3) != -1); 258 | ASSERT(ecs_type_index_of(type, 5) != -1); 259 | ecs_type_free(type); 260 | PASS(); 261 | } 262 | 263 | TEST type_equal() { 264 | ecs_type_t *a = ecs_type_new(8); 265 | ecs_type_add(a, 1); 266 | ecs_type_add(a, 2); 267 | ecs_type_add(a, 3); 268 | ecs_type_t *b = ecs_type_new(8); 269 | ecs_type_add(b, 3); 270 | ecs_type_add(b, 1); 271 | ecs_type_add(b, 2); 272 | ASSERT(ecs_type_equal(a, a)); 273 | ASSERT(ecs_type_equal(b, b)); 274 | ASSERT(ecs_type_equal(a, b)); 275 | ecs_type_free(a); 276 | ecs_type_free(b); 277 | PASS(); 278 | } 279 | 280 | TEST type_copy() { 281 | ecs_type_t *a = ecs_type_new(8); 282 | ecs_type_add(a, 1); 283 | ecs_type_add(a, 2); 284 | ecs_type_add(a, 3); 285 | ecs_type_t *b = ecs_type_copy(a); 286 | ASSERT(ecs_type_equal(a, b)); 287 | ecs_type_remove(b, 1); 288 | ASSERT_FALSE(ecs_type_equal(a, b)); 289 | ecs_type_free(a); 290 | ecs_type_free(b); 291 | PASS(); 292 | } 293 | 294 | TEST type_superset() { 295 | ecs_type_t *a = ecs_type_new(8); 296 | ecs_type_add(a, 1); 297 | ecs_type_add(a, 2); 298 | ecs_type_add(a, 3); 299 | ecs_type_t *b = ecs_type_copy(a); 300 | ecs_type_add(b, 5); 301 | ecs_type_add(b, 6); 302 | ecs_type_add(b, 7); 303 | ASSERT(ecs_type_is_superset(b, a)); 304 | ecs_type_free(a); 305 | ecs_type_free(b); 306 | PASS(); 307 | } 308 | 309 | SUITE(type) { 310 | RUN_TEST(type_empty); 311 | RUN_TEST(type_contains); 312 | RUN_TEST(type_add_1); 313 | for (int i = 10; i <= 1000; i *= 10) { 314 | RUN_TEST1(type_add_multiple, i); 315 | RUN_TEST1(type_add_multiple_reversed, i); 316 | RUN_TEST1(type_add_multiple_random, i); 317 | } 318 | RUN_TEST(type_add_duplicate); 319 | RUN_TEST(type_remove_from_empty); 320 | RUN_TEST(type_remove_from_1); 321 | RUN_TEST(type_remove_from_many); 322 | RUN_TEST(type_equal); 323 | RUN_TEST(type_copy); 324 | // RUN_TEST(type_superset); 325 | (void)type_superset; 326 | } 327 | 328 | TEST ecs_minimal() { 329 | ecs_registry_t *registry = ecs_init(); 330 | ecs_destroy(registry); 331 | PASS(); 332 | } 333 | 334 | TEST ecs_register() { 335 | ecs_registry_t *registry = ecs_init(); 336 | ECS_COMPONENT(registry, int); 337 | ecs_destroy(registry); 338 | PASS(); 339 | } 340 | 341 | TEST ecs_create_entity() { 342 | ecs_registry_t *registry = ecs_init(); 343 | ecs_entity_t e = ecs_entity(registry); 344 | (void)e; 345 | ecs_destroy(registry); 346 | PASS(); 347 | } 348 | 349 | TEST ecs_attach_component() { 350 | ecs_registry_t *registry = ecs_init(); 351 | ecs_entity_t int_component = ECS_COMPONENT(registry, int); 352 | ecs_entity_t e = ecs_entity(registry); 353 | ecs_attach(registry, e, int_component); 354 | ecs_destroy(registry); 355 | PASS(); 356 | } 357 | 358 | TEST ecs_set_component_data() { 359 | ecs_registry_t *registry = ecs_init(); 360 | ecs_entity_t int_component = ECS_COMPONENT(registry, int); 361 | ecs_entity_t e = ecs_entity(registry); 362 | ecs_attach(registry, e, int_component); 363 | ecs_set(registry, e, int_component, &(int){1}); 364 | ecs_destroy(registry); 365 | PASS(); 366 | } 367 | 368 | void print(ecs_view_t view, unsigned int row) { 369 | int *x = ecs_view(view, row, 0); 370 | printf("x is: %d\n", *x); 371 | } 372 | 373 | TEST ecs_run_system() { 374 | ecs_registry_t *registry = ecs_init(); 375 | ecs_entity_t int_component = ECS_COMPONENT(registry, int); 376 | ecs_entity_t e = ecs_entity(registry); 377 | ecs_attach(registry, e, int_component); 378 | ecs_set(registry, e, int_component, &(int){0}); 379 | ecs_signature_t *sig = ecs_signature_new_n(1, int_component); 380 | ecs_system(registry, sig, print); 381 | ecs_step(registry); 382 | ecs_destroy(registry); 383 | PASS(); 384 | } 385 | 386 | void move(ecs_view_t view, unsigned int row) { 387 | int *p = ecs_view(view, row, 0); 388 | int *v = ecs_view(view, row, 1); 389 | *p += *v; 390 | // printf("p is: %d\n", *p); 391 | } 392 | 393 | TEST ecs_run_system_loop() { 394 | typedef int pos; 395 | typedef int vel; 396 | 397 | ecs_registry_t *registry = ecs_init(); 398 | ecs_entity_t pos_component = ECS_COMPONENT(registry, pos); 399 | ecs_entity_t vel_component = ECS_COMPONENT(registry, vel); 400 | ecs_entity_t e = ecs_entity(registry); 401 | ecs_attach(registry, e, pos_component); 402 | ecs_attach(registry, e, vel_component); 403 | ecs_set(registry, e, pos_component, &(pos){0}); 404 | ecs_set(registry, e, vel_component, &(pos){1}); 405 | ECS_SYSTEM(registry, move, 2, pos_component, vel_component); 406 | 407 | for (int i = 0; i < 15; i++) { 408 | ecs_step(registry); 409 | } 410 | 411 | ecs_destroy(registry); 412 | PASS(); 413 | } 414 | 415 | typedef float Position; 416 | typedef float Velocity; 417 | 418 | void do_ecs_move(ecs_view_t view, unsigned int row) { 419 | Position *p = (Position *)ecs_view(view, row, 0); 420 | Velocity *v = (Velocity *)ecs_view(view, row, 1); 421 | *p += *v; 422 | printf("%f, %f\n", *p, *v); 423 | } 424 | 425 | TEST ecs_from_bench(int context[]) { 426 | ecs_registry_t *registry = ecs_init(); 427 | 428 | int entities = context[0]; 429 | int iterations = context[1]; 430 | 431 | const ecs_entity_t pos_component = ECS_COMPONENT(registry, Position); 432 | const ecs_entity_t vel_component = ECS_COMPONENT(registry, Velocity); 433 | 434 | for (int i = 0; i < entities; i++) { 435 | ecs_entity_t e = ecs_entity(registry); 436 | ecs_attach(registry, e, pos_component); 437 | ecs_attach(registry, e, vel_component); 438 | Position initial_position = 0; 439 | Velocity initial_velocity = 1; 440 | ecs_set(registry, e, pos_component, &initial_position); 441 | ecs_set(registry, e, vel_component, &initial_velocity); 442 | } 443 | 444 | ECS_SYSTEM(registry, do_ecs_move, 2, pos_component, vel_component); 445 | 446 | for (int i = 0; i < iterations; i++) { 447 | ecs_step(registry); 448 | } 449 | 450 | ecs_destroy(registry); 451 | PASS(); 452 | } 453 | 454 | SUITE(ecs) { 455 | RUN_TEST(ecs_run_system_loop); 456 | RUN_TEST(ecs_run_system); 457 | RUN_TEST(ecs_minimal); 458 | RUN_TEST(ecs_register); 459 | RUN_TEST(ecs_create_entity); 460 | RUN_TEST(ecs_attach_component); 461 | RUN_TEST(ecs_set_component_data); 462 | RUN_TEST1(ecs_from_bench, ((int[2]){10, 1000})); 463 | } 464 | 465 | GREATEST_MAIN_DEFS(); 466 | 467 | int main(int argc, char *argv[]) { 468 | srand(time(NULL)); 469 | 470 | GREATEST_MAIN_BEGIN(); 471 | RUN_SUITE(map); 472 | RUN_SUITE(type); 473 | RUN_SUITE(ecs); 474 | GREATEST_MAIN_END(); 475 | } 476 | --------------------------------------------------------------------------------