From 2ed30603c14a7b73e9ac6decc7fa72159e0763b0 Mon Sep 17 00:00:00 2001 From: Vladyslav Hrytsenko Date: Sun, 17 Jan 2021 16:23:23 +0200 Subject: [PATCH] code: added flecs, integrated with librg --- code/apps/server/CMakeLists.txt | 3 + code/apps/server/header/components.h | 24 + code/apps/server/header/utils/options.h | 2 +- code/apps/server/header/world/world.h | 8 +- code/apps/server/source/main.c | 22 +- code/apps/server/source/network.c | 86 +- code/apps/server/source/utils/options.c | 8 +- code/apps/server/source/world/world.c | 120 +- code/common/packets/packet.h | 1 + code/vendors/flecs/flecs.c | 23349 ++++++++++++++++++++++ code/vendors/flecs/flecs.h | 12994 ++++++++++++ 11 files changed, 36508 insertions(+), 109 deletions(-) create mode 100644 code/apps/server/header/components.h create mode 100644 code/vendors/flecs/flecs.c create mode 100644 code/vendors/flecs/flecs.h diff --git a/code/apps/server/CMakeLists.txt b/code/apps/server/CMakeLists.txt index 2d39c5c..b5ae915 100644 --- a/code/apps/server/CMakeLists.txt +++ b/code/apps/server/CMakeLists.txt @@ -15,6 +15,9 @@ add_executable(eco2d-server ../../vendors/cwpack/cwpack.c ../../vendors/cwpack/cwpack.h + + ../../vendors/flecs/flecs.c + ../../vendors/flecs/flecs.h ) include_directories(eco2d-server header) diff --git a/code/apps/server/header/components.h b/code/apps/server/header/components.h new file mode 100644 index 0000000..e770f77 --- /dev/null +++ b/code/apps/server/header/components.h @@ -0,0 +1,24 @@ +#pragma once +#include "flecs/flecs.h" + +typedef struct { + int16_t x, y; +} chunk; + +typedef struct { + float x, y; +} position; + +typedef struct { + uint16_t peer_id; +} netclient; + +ECS_COMPONENT_DECLARE(chunk); +ECS_COMPONENT_DECLARE(position); +ECS_COMPONENT_DECLARE(netclient); + +static inline void components_register(ecs_world_t *ecs) { + ECS_COMPONENT_DEFINE(ecs, chunk); + ECS_COMPONENT_DEFINE(ecs, position); + ECS_COMPONENT_DEFINE(ecs, netclient); +} diff --git a/code/apps/server/header/utils/options.h b/code/apps/server/header/utils/options.h index 272c873..d647f18 100644 --- a/code/apps/server/header/utils/options.h +++ b/code/apps/server/header/utils/options.h @@ -1,4 +1,4 @@ #pragma once #include "system.h" -void generate_minimap(int32_t seed, uint16_t, uint16_t); +void generate_minimap(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint16_t world_size); diff --git a/code/apps/server/header/world/world.h b/code/apps/server/header/world/world.h index 79b2d50..e4e5a94 100644 --- a/code/apps/server/header/world/world.h +++ b/code/apps/server/header/world/world.h @@ -1,13 +1,19 @@ #pragma once #include "system.h" +#include "librg.h" +#include "flecs/flecs.h" #define WORLD_ERROR_NONE +0x0000 #define WORLD_ERROR_OUTOFMEM -0x0001 #define WORLD_ERROR_INVALID_BLOCKS -0x0002 #define WORLD_ERROR_INVALID_DIMENSIONS -0x0003 #define WORLD_ERROR_INVALID_BUFFER -0x0004 +#define WORLD_ERROR_TRACKER_FAILED -0x0005 -int32_t world_init(int32_t seed, uint16_t chunk_width, uint16_t chunk_height, uint16_t chunk_amountx, uint16_t chunk_amounty); +int32_t world_init(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint16_t world_size); int32_t world_destroy(void); +int32_t world_update(void); uint32_t world_buf(uint8_t const **ptr, uint32_t *width); +ecs_world_t * world_ecs(void); +librg_world * world_tracker(void); diff --git a/code/apps/server/source/main.c b/code/apps/server/source/main.c index dea4827..79e0dbc 100644 --- a/code/apps/server/source/main.c +++ b/code/apps/server/source/main.c @@ -7,8 +7,9 @@ #include "utils/options.h" #define DEFAULT_WORLD_SEED 302097 -#define DEFAULT_CHUNK_SIZE 16 -#define DEFAULT_CHUNK_AMOUNT 8 +#define DEFAULT_BLOCK_SIZE 64 /* amount of units within a block (single axis) */ +#define DEFAULT_CHUNK_SIZE 16 /* amount of blocks within a chunk (single axis) */ +#define DEFAULT_WORLD_SIZE 8 /* amount of chunks within a world (single axis) */ #define IF(call) do { \ if (call != 0) { \ @@ -25,8 +26,10 @@ int main(int argc, char** argv) { zpl_opts_add(&opts, "p", "preview-map", "draw world preview", ZPL_OPTS_FLAG); zpl_opts_add(&opts, "s", "seed", "world seed", ZPL_OPTS_INT); zpl_opts_add(&opts, "r", "random-seed", "generate random world seed", ZPL_OPTS_FLAG); - zpl_opts_add(&opts, "cs", "chunk-size", "size of a single chunk", ZPL_OPTS_INT); - zpl_opts_add(&opts, "ca", "chunk-amount", "amount of chunks", ZPL_OPTS_INT); + zpl_opts_add(&opts, "bs", "block-size", "amount of units within a block (single axis)", ZPL_OPTS_INT); + zpl_opts_add(&opts, "cs", "chunk-size", "amount of blocks within a chunk (single axis)", ZPL_OPTS_INT); + zpl_opts_add(&opts, "ws", "world-size", "amount of chunks within a world (single axis)", ZPL_OPTS_INT); + uint32_t ok = zpl_opts_compile(&opts, argc, argv); if (!ok) { @@ -36,9 +39,9 @@ int main(int argc, char** argv) { } int32_t seed = zpl_opts_integer(&opts, "seed", DEFAULT_WORLD_SEED); - int32_t chunk_size = zpl_opts_integer(&opts, "chunk-size", DEFAULT_CHUNK_SIZE); - int32_t chunk_amount = zpl_opts_integer(&opts, "chunk-amount", DEFAULT_CHUNK_AMOUNT); - int32_t world_size = chunk_size * chunk_amount; + uint16_t block_size = zpl_opts_integer(&opts, "block-size", DEFAULT_BLOCK_SIZE); + uint16_t chunk_size = zpl_opts_integer(&opts, "chunk-size", DEFAULT_CHUNK_SIZE); + uint16_t world_size = zpl_opts_integer(&opts, "world-size", DEFAULT_WORLD_SIZE); if (zpl_opts_has_arg(&opts, "random-seed")) { zpl_random rnd={0}; @@ -48,12 +51,12 @@ int main(int argc, char** argv) { } if (zpl_opts_has_arg(&opts, "preview-map")) { - generate_minimap(seed, chunk_size, chunk_amount); + generate_minimap(seed, block_size, chunk_size, world_size); return 0; } zpl_printf("[INFO] Generating world of size: %d x %d\n", world_size, world_size); - IF(world_init(seed, chunk_size, chunk_size, chunk_amount, chunk_amount)); + IF(world_init(seed, block_size, chunk_size, world_size)); zpl_printf("[INFO] Initializing network...\n"); IF(network_init()); @@ -61,6 +64,7 @@ int main(int argc, char** argv) { while (true) { network_server_tick(); + world_update(); } IF(network_server_stop()); diff --git a/code/apps/server/source/network.c b/code/apps/server/source/network.c index 323213e..9d448c4 100644 --- a/code/apps/server/source/network.c +++ b/code/apps/server/source/network.c @@ -9,12 +9,13 @@ #include "system.h" #include "network.h" +#include "components.h" +#include "world/world.h" #define NETWORK_UPDATE_DELAY 0.100 #define NETWORK_MAX_CLIENTS 32 static ENetHost *server = NULL; -static librg_world *server_world = NULL; static zpl_timer nettimer = {0}; int32_t network_init(void) { @@ -28,34 +29,6 @@ int32_t network_destroy(void) { return 0; } -int32_t server_write_update(librg_world *w, librg_event *e) { - int64_t owner_id = librg_event_owner_get(w, e); - int64_t entity_id = librg_event_entity_get(w, e); - - return 0; - - // /* prevent sending updates to users who own that entity */ - // /* since they will be responsible on telling where that entity is supposed to be */ - // if (librg_entity_owner_get(w, entity_id) == owner_id) { - // return LIBRG_WRITE_REJECT; - // } - - // /* read our current position */ - // ENetPeer *peer = (ENetPeer *)librg_entity_userdata_get(w, entity_id); - - // char *buffer = librg_event_buffer_get(w, e); - // size_t max_length = librg_event_size_get(w, e); - - // /* check if we have enough space to write and valid position */ - // if (sizeof(vec3) > max_length || !peer->data) { - // return LIBRG_WRITE_REJECT; - // } - - // /* write data and return how much we've written */ - // memcpy(buffer, peer->data, sizeof(vec3)); - // return sizeof(vec3); -} - int32_t network_server_start(const char *host, uint16_t port) { zpl_unused(host); @@ -73,26 +46,6 @@ int32_t network_server_start(const char *host, uint16_t port) { } zpl_printf("[INFO] Started an ENet server...\n"); - server_world = librg_world_create(); - - if (server_world == NULL) { - zpl_printf("[ERROR] An error occurred while trying to create a server world.\n"); - return 1; - } - - zpl_printf("[INFO] Created a new server world\n"); - - /* store our host to the userdata */ - librg_world_userdata_set(server_world, server); - - /* config our world grid */ - librg_config_chunksize_set(server_world, 16, 16, 16); - librg_config_chunkamount_set(server_world, 9, 9, 9); - librg_config_chunkoffset_set(server_world, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID); - - librg_event_set(server_world, LIBRG_WRITE_UPDATE, server_write_update); - // librg_event_set(server_world, LIBRG_READ_UPDATE, server_read_update); - zpl_timer_start(&nettimer, NETWORK_UPDATE_DELAY); return 0; @@ -101,11 +54,7 @@ int32_t network_server_start(const char *host, uint16_t port) { int32_t network_server_stop(void) { zpl_timer_stop(&nettimer); enet_host_destroy(server); - librg_world_destroy(server_world); - - server_world = NULL; server = NULL; - return 0; } @@ -115,29 +64,32 @@ int32_t network_server_tick(void) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { zpl_printf("[INFO] A new user %d connected.\n", event.peer->incomingPeerID); - int64_t entity_id = event.peer->incomingPeerID; + uint16_t peer_id = event.peer->incomingPeerID; - // /* we create an entity for our client */ - // /* in our case it is going to have same id as owner id */ - // /* since we do not really plan on adding more entities per client for now */ - // /* and place his entity right in the centerl of the world */ - // librg_entity_track(server_world, entity_id); - // librg_entity_owner_set(server_world, entity_id, event.peer->incomingPeerID); - // librg_entity_chunk_set(server_world, entity_id, 1); - // librg_entity_radius_set(server_world, entity_id, 2); /* 2 chunk radius visibility */ - // librg_entity_userdata_set(server_world, entity_id, event.peer); /* save ptr to peer */ + ecs_entity_t e = ecs_new(world_ecs(), 0); + ecs_set(world_ecs(), e, position, {0, 0}); + ecs_set(world_ecs(), e, netclient, {peer_id}); + + event.peer->data = (void*)e; + + librg_entity_track(world_tracker(), e); + librg_entity_owner_set(world_tracker(), e, peer_id); + librg_entity_chunk_set(world_tracker(), e, 1); + librg_entity_radius_set(world_tracker(), e, 2); /* 2 chunk radius visibility */ + librg_entity_userdata_set(world_tracker(), e, event.peer); /* save ptr to peer */ } break; case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { zpl_printf("[INFO] A user %d disconnected.\n", event.peer->incomingPeerID); - int64_t entity_id = event.peer->incomingPeerID; - // librg_entity_untrack(server_world, entity_id); + ecs_entity_t e = (ecs_entity_t)event.peer->data; + librg_entity_untrack(world_tracker(), e); + ecs_delete(world_ecs(), e); } break; case ENET_EVENT_TYPE_RECEIVE: { // /* handle a newly received event */ // librg_world_read( - // server_world, + // world_tracker(), // event.peer->incomingPeerID, // (char *)event.packet->data, // event.packet->dataLength, @@ -170,7 +122,7 @@ void network_server_update(void *data) { // /* serialize peer's the world view to a buffer */ // librg_world_write( - // server_world, + // world_tracker(), // currentPeer->incomingPeerID, // buffer, // &buffer_length, diff --git a/code/apps/server/source/utils/options.c b/code/apps/server/source/utils/options.c index 229e487..abeded0 100644 --- a/code/apps/server/source/utils/options.c +++ b/code/apps/server/source/utils/options.c @@ -4,15 +4,15 @@ #include "world/blocks.h" #include "utils/options.h" -void generate_minimap(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) { - world_init(seed, chunk_size, chunk_size, chunk_amount, chunk_amount); +void generate_minimap(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint16_t world_size) { + world_init(seed, block_size, chunk_size, world_size); uint8_t const *world; - uint32_t world_size = chunk_size * chunk_amount; + uint32_t world_length = chunk_size * world_size; uint32_t len = world_buf(&world, NULL); for (int i=0; i 0 && i % world_size == 0) { + if (i > 0 && i % world_length == 0) { putc('\n', stdout); } putc(blocks_get_symbol(world[i]), stdout); diff --git a/code/apps/server/source/world/world.c b/code/apps/server/source/world/world.c index f4f112a..fe75dc4 100644 --- a/code/apps/server/source/world/world.c +++ b/code/apps/server/source/world/world.c @@ -1,4 +1,6 @@ #include "zpl.h" +#include "librg.h" +#include "components.h" #include "world/world.h" typedef struct { @@ -7,39 +9,114 @@ typedef struct { uint32_t size; uint32_t width; uint32_t height; - uint16_t chunk_width; - uint16_t chunk_height; - uint16_t chunk_amountx; - uint16_t chunk_amounty; + uint16_t block_size; + uint16_t chunk_size; + uint16_t world_size; + ecs_world_t *ecs; + librg_world *tracker; } world_data; + static world_data world = {0}; int32_t world_gen(); -int32_t world_init(int32_t seed, uint16_t chunk_width, uint16_t chunk_height, uint16_t chunk_amountx, uint16_t chunk_amounty) { +int32_t world_write_update(librg_world *w, librg_event *e) { + int64_t owner_id = librg_event_owner_get(w, e); + int64_t entity_id = librg_event_entity_get(w, e); + + return 0; + + // /* prevent sending updates to users who own that entity */ + // /* since they will be responsible on telling where that entity is supposed to be */ + // if (librg_entity_owner_get(w, entity_id) == owner_id) { + // return LIBRG_WRITE_REJECT; + // } + + // /* read our current position */ + // ENetPeer *peer = (ENetPeer *)librg_entity_userdata_get(w, entity_id); + + // char *buffer = librg_event_buffer_get(w, e); + // size_t max_length = librg_event_size_get(w, e); + + // /* check if we have enough space to write and valid position */ + // if (sizeof(vec3) > max_length || !peer->data) { + // return LIBRG_WRITE_REJECT; + // } + + // /* write data and return how much we've written */ + // memcpy(buffer, peer->data, sizeof(vec3)); + // return sizeof(vec3); +} + +int32_t world_init(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint16_t world_size) { if (world.data) { world_destroy(); } + world.seed = seed; - world.width = chunk_width * chunk_amountx; - world.height = chunk_height * chunk_amounty; - world.size = world.width*world.height; + world.block_size = block_size; + world.chunk_size = chunk_size; + world.world_size = world_size; + + world.width = chunk_size * world_size; + world.height = chunk_size * world_size; + world.size = world.width * world.height; world.data = zpl_malloc(sizeof(uint8_t)*world.size); if (!world.data) { return WORLD_ERROR_OUTOFMEM; } + world.ecs = ecs_init(); + components_register(world.ecs); + + world.tracker = librg_world_create(); + + if (world.tracker == NULL) { + zpl_printf("[ERROR] An error occurred while trying to create a server world.\n"); + return WORLD_ERROR_TRACKER_FAILED; + } + + zpl_printf("[INFO] Created a new server world\n"); + + /* config our world grid */ + librg_config_chunksize_set(world.tracker, block_size * chunk_size, block_size * chunk_size, 1); + librg_config_chunkamount_set(world.tracker, world_size, world_size, 1); + librg_config_chunkoffset_set(world.tracker, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID); + + for (int i = 0; i < chunk_size * chunk_size; ++i) { + ecs_entity_t e = ecs_new(world.ecs, 0); + ecs_set(world.ecs, e, chunk, { + .x = i % chunk_size, + .y = i / chunk_size, + }); + + librg_entity_track(world.tracker, e); + librg_entity_chunk_set(world.tracker, e, i); + + zpl_printf("creating chunk: #%lld %d %d\n", e, i % chunk_size, i / chunk_size); + } + + // librg_event_set(world.tracker, LIBRG_WRITE_UPDATE, world_write_update); + // librg_event_set(world.tracker, LIBRG_READ_UPDATE, server_read_update); + return world_gen(); } int32_t world_destroy(void) { + librg_world_destroy(world.tracker); + ecs_fini(world.ecs); zpl_mfree(world.data); zpl_memset(&world, 0, sizeof(world)); return WORLD_ERROR_NONE; } +int32_t world_update() { + ecs_progress(world.ecs, 0); + return 0; +} + uint32_t world_buf(uint8_t const **ptr, uint32_t *width) { ZPL_ASSERT_NOT_NULL(world.data); ZPL_ASSERT_NOT_NULL(ptr); @@ -48,23 +125,12 @@ uint32_t world_buf(uint8_t const **ptr, uint32_t *width) { return world.size; } +ecs_world_t * world_ecs() { + return world.ecs; +} + +librg_world * world_tracker() { + return world.tracker; +} + #include "world_gen.c" - - - -// 11111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222333333333333333333333333333333333333333 - -// world 3x3 -// chunk 3x3 - -// 111 111 111 -// 222 222 222 -// 333 333 333 - -// 111 111 111 -// 222 222 222 -// 333 333 333 - -// 111 111 111 -// 222 222 222 -// 333 333 333 diff --git a/code/common/packets/packet.h b/code/common/packets/packet.h index e69de29..e9b6a44 100644 --- a/code/common/packets/packet.h +++ b/code/common/packets/packet.h @@ -0,0 +1 @@ +// nothing diff --git a/code/vendors/flecs/flecs.c b/code/vendors/flecs/flecs.c new file mode 100644 index 0000000..1c86a58 --- /dev/null +++ b/code/vendors/flecs/flecs.c @@ -0,0 +1,23349 @@ +#ifndef FLECS_IMPL +#include "flecs.h" +#endif +#ifndef FLECS_PRIVATE_H +#define FLECS_PRIVATE_H + +#ifndef FLECS_TYPES_PRIVATE_H +#define FLECS_TYPES_PRIVATE_H + +#ifndef __MACH__ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +//FIXME +#else +#include /* attempt to define endianness */ +#endif +#ifdef linux +# include /* attempt to define endianness */ +#endif + +#ifndef FLECS_ENTITY_INDEX_H +#define FLECS_ENTITY_INDEX_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#define ecs_eis_get(world, entity) ecs_sparse_get_sparse((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_get_any(world, entity) ecs_sparse_get_sparse_any((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_set(world, entity, ...) (ecs_sparse_set((world->store).entity_index, ecs_record_t, entity, (__VA_ARGS__))) +#define ecs_eis_get_or_create(world, entity) ecs_sparse_get_or_create((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_delete(world, entity) ecs_sparse_remove((world->store).entity_index, entity) +#define ecs_eis_set_generation(world, entity) ecs_sparse_set_generation((world->store).entity_index, entity) +#define ecs_eis_is_alive(world, entity) ecs_sparse_is_alive((world->store).entity_index, entity) +#define ecs_eis_exists(world, entity) ecs_sparse_exists((world->store).entity_index, entity) +#define ecs_eis_recycle(world) ecs_sparse_new_id((world->store).entity_index) +#define ecs_eis_clear_entity(world, entity, is_watched) ecs_eis_set((world->store).entity_index, entity, &(ecs_record_t){NULL, is_watched}) +#define ecs_eis_set_size(world, size) ecs_sparse_set_size((world->store).entity_index, size) +#define ecs_eis_count(world) ecs_sparse_count((world->store).entity_index) +#define ecs_eis_clear(world) ecs_sparse_clear((world->store).entity_index) +#define ecs_eis_copy(world) ecs_sparse_copy((world->store).entity_index) +#define ecs_eis_free(world) ecs_sparse_free((world->store).entity_index) +#define ecs_eis_memory(world, allocd, used) ecs_sparse_memory((world->store).entity_index, allocd, used) + +#ifdef __cplusplus +} +#endif + +#endif + +#define ECS_MAX_JOBS_PER_WORKER (16) + +/** These values are used to verify validity of the pointers passed into the API + * and to allow for passing a thread as a world to some API calls (this allows + * for transparently passing thread context to API functions) */ +#define ECS_WORLD_MAGIC (0x65637377) +#define ECS_THREAD_MAGIC (0x65637374) + +/* Maximum number of entities that can be added in a single operation. + * Increasing this value will increase consumption of stack space. */ +#define ECS_MAX_ADD_REMOVE (32) + +/* Maximum length of an entity name, including 0 terminator */ +#define ECS_MAX_NAME_LENGTH (64) + +/** Callback used by the system signature expression parser. */ +typedef int (*ecs_parse_action_t)( + ecs_world_t *world, + const char *id, + const char *expr, + int64_t column, + ecs_sig_from_kind_t from_kind, + ecs_sig_oper_kind_t oper_kind, + ecs_sig_inout_kind_t inout_kind, + ecs_entity_t flags, + const char *component, + const char *source, + const char *trait, + const char *name, + void *ctx); + +/** Component-specific data */ +typedef struct ecs_c_info_t { + ecs_entity_t component; + ecs_vector_t *on_add; /* Systems ran after adding this component */ + ecs_vector_t *on_remove; /* Systems ran after removing this component */ + EcsComponentLifecycle lifecycle; /* Component lifecycle callbacks */ + bool lifecycle_set; +} ecs_c_info_t; + +/* Table event type for notifying tables of world events */ +typedef enum ecs_table_eventkind_t { + EcsTableQueryMatch, + EcsTableQueryUnmatch, + EcsTableComponentInfo +} ecs_table_eventkind_t; + +typedef struct ecs_table_event_t { + ecs_table_eventkind_t kind; + + /* Query event */ + ecs_query_t *query; + int32_t matched_table_index; + + /* Component info event */ + ecs_entity_t component; + + /* If the nubmer of fields gets out of hand, this can be turned into a union + * but since events are very temporary objects, this works for now and makes + * initializing an event a bit simpler. */ +} ecs_table_event_t; + +/** A component column. */ +struct ecs_column_t { + ecs_vector_t *data; /**< Column data */ + int16_t size; /**< Column element size */ + int16_t alignment; /**< Column element alignment */ +}; + +/** A switch column. */ +typedef struct ecs_sw_column_t { + ecs_switch_t *data; /**< Column data */ + ecs_type_t type; /**< Switch type */ +} ecs_sw_column_t; + +/** A bitset column. */ +typedef struct ecs_bs_column_t { + ecs_bitset_t data; /**< Column data */ +} ecs_bs_column_t; + +/** Stage-specific component data */ +struct ecs_data_t { + ecs_vector_t *entities; /**< Entity identifiers */ + ecs_vector_t *record_ptrs; /**< Ptrs to records in main entity index */ + ecs_column_t *columns; /**< Component columns */ + ecs_sw_column_t *sw_columns; /**< Switch columns */ + ecs_bs_column_t *bs_columns; /**< Bitset columns */ +}; + +/** Small footprint data structure for storing data associated with a table. */ +typedef struct ecs_table_leaf_t { + ecs_table_t *table; + ecs_type_t type; + ecs_data_t *data; +} ecs_table_leaf_t; + +/** Flags for quickly checking for special properties of a table. */ +#define EcsTableHasBuiltins 1u /**< Does table have builtin components */ +#define EcsTableIsPrefab 2u /**< Does the table store prefabs */ +#define EcsTableHasBase 4u /**< Does the table type has INSTANCEOF */ +#define EcsTableHasParent 8u /**< Does the table type has CHILDOF */ +#define EcsTableHasComponentData 16u /**< Does the table have component data */ +#define EcsTableHasXor 32u /**< Does the table type has XOR */ +#define EcsTableIsDisabled 64u /**< Does the table type has EcsDisabled */ +#define EcsTableHasCtors 128u +#define EcsTableHasDtors 256u +#define EcsTableHasCopy 512u +#define EcsTableHasMove 1024u +#define EcsTableHasOnAdd 2048u +#define EcsTableHasOnRemove 4096u +#define EcsTableHasOnSet 8192u +#define EcsTableHasUnSet 16384u +#define EcsTableHasMonitors 32768u +#define EcsTableHasSwitch 65536u +#define EcsTableHasDisabled 131072u + +/* Composite constants */ +#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) +#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasSwitch | EcsTableHasDisabled) +#define EcsTableHasAddActions (EcsTableHasBase | EcsTableHasSwitch | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet | EcsTableHasMonitors) +#define EcsTableHasRemoveActions (EcsTableHasBase | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet | EcsTableHasMonitors) + +/** Edge used for traversing the table graph. */ +typedef struct ecs_edge_t { + ecs_table_t *add; /**< Edges traversed when adding */ + ecs_table_t *remove; /**< Edges traversed when removing */ +} ecs_edge_t; + +/** Quey matched with table with backref to query table administration. + * This type is used to store a matched query together with the array index of + * where the table is stored in the query administration. This type is used when + * an action that originates on a table needs to invoke a query (system) and a + * fast lookup is required for the query administration, as is the case with + * OnSet and Monitor systems. */ +typedef struct ecs_matched_query_t { + ecs_query_t *query; /**< The query matched with the table */ + int32_t matched_table_index; /**< Table index in the query type */ +} ecs_matched_query_t; + +/** A table is the Flecs equivalent of an archetype. Tables store all entities + * with a specific set of components. Tables are automatically created when an + * entity has a set of components not previously observed before. When a new + * table is created, it is automatically matched with existing queries */ +struct ecs_table_t { + ecs_type_t type; /**< Identifies table type in type_index */ + ecs_c_info_t **c_info; /**< Cached pointers to component info */ + + ecs_edge_t *lo_edges; /**< Edges to other tables */ + ecs_map_t *hi_edges; + + ecs_data_t *data; /**< Component storage */ + + ecs_vector_t *queries; /**< Queries matched with table */ + ecs_vector_t *monitors; /**< Monitor systems matched with table */ + ecs_vector_t **on_set; /**< OnSet systems, broken up by column */ + ecs_vector_t *on_set_all; /**< All OnSet systems */ + ecs_vector_t *on_set_override; /**< All OnSet systems with overrides */ + ecs_vector_t *un_set_all; /**< All UnSet systems */ + + int32_t *dirty_state; /**< Keep track of changes in columns */ + int32_t alloc_count; /**< Increases when columns are reallocd */ + uint32_t id; /**< Table id in sparse set */ + + ecs_flags32_t flags; /**< Flags for testing table properties */ + + int32_t column_count; /**< Number of data columns in table */ + int32_t sw_column_count; + int32_t sw_column_offset; + int32_t bs_column_count; + int32_t bs_column_offset; +}; + +/* Sparse query column */ +typedef struct ecs_sparse_column_t { + ecs_sw_column_t *sw_column; + ecs_entity_t sw_case; + int32_t signature_column_index; +} ecs_sparse_column_t; + +/* Bitset query column */ +typedef struct ecs_bitset_column_t { + ecs_bs_column_t *bs_column; + int32_t column_index; +} ecs_bitset_column_t; + +/** Type containing data for a table matched with a query. */ +typedef struct ecs_matched_table_t { + ecs_iter_table_t iter_data; /**< Precomputed data for iterators */ + ecs_vector_t *sparse_columns; /**< Column ids of sparse columns */ + ecs_vector_t *bitset_columns; /**< Column ids with disabled flags */ + int32_t *monitor; /**< Used to monitor table for changes */ + int32_t rank; /**< Rank used to sort tables */ +} ecs_matched_table_t; + +/** Type used to track location of table in queries' table lists. + * When a table becomes empty or non-empty a signal is sent to a query, which + * moves the table to or from an empty list. While this ensures that when + * iterating no time is spent on iterating over empty tables, doing a linear + * search for the table in either list can take a significant amount of time if + * a query is matched with many tables. + * + * To avoid a linear search, the query has a map with table indices that can + * return the location of the table in either list in constant time. + * + * If a table is matched multiple times by a query, such as can happen when a + * query matches traits, a table can occupy multiple indices. + */ +typedef struct ecs_table_indices_t { + int32_t *indices; /* If indices are negative, table is in empty list */ + int32_t count; +} ecs_table_indices_t; + +/** Type storing an entity range within a table. + * This type is used for iterating in orer across archetypes. A sorting function + * constructs a list of the ranges across archetypes that are in order so that + * when the query iterates over the archetypes, it only needs to iterate the + * list of ranges. */ +typedef struct ecs_table_slice_t { + ecs_matched_table_t *table; /**< Reference to the matched table */ + int32_t start_row; /**< Start of range */ + int32_t count; /**< Number of entities in range */ +} ecs_table_slice_t; + +#define EcsQueryNeedsTables (1) /* Query needs matching with tables */ +#define EcsQueryMonitor (2) /* Query needs to be registered as a monitor */ +#define EcsQueryOnSet (4) /* Query needs to be registered as on_set system */ +#define EcsQueryUnSet (8) /* Query needs to be registered as un_set system */ +#define EcsQueryMatchDisabled (16) /* Does query match disabled */ +#define EcsQueryMatchPrefab (32) /* Does query match prefabs */ +#define EcsQueryHasRefs (64) /* Does query have references */ +#define EcsQueryHasTraits (128) /* Does query have traits */ +#define EcsQueryIsSubquery (256) /* Is query a subquery */ +#define EcsQueryIsOrphaned (512) /* Is subquery orphaned */ +#define EcsQueryHasOutColumns (1024) /* Does query have out columns */ +#define EcsQueryHasOptional (2048) /* Does query have optional columns */ + +#define EcsQueryNoActivation (EcsQueryMonitor | EcsQueryOnSet | EcsQueryUnSet) + +/* Query event type for notifying queries of world events */ +typedef enum ecs_query_eventkind_t { + EcsQueryTableMatch, + EcsQueryTableEmpty, + EcsQueryTableNonEmpty, + EcsQueryTableRematch, + EcsQueryTableUnmatch, + EcsQueryOrphan +} ecs_query_eventkind_t; + +typedef struct ecs_query_event_t { + ecs_query_eventkind_t kind; + ecs_table_t *table; + ecs_query_t *parent_query; +} ecs_query_event_t; + +/** Query that is automatically matched against active tables */ +struct ecs_query_t { + /* Signature of query */ + ecs_sig_t sig; + + /* Reference to world */ + ecs_world_t *world; + + /* Tables matched with query */ + ecs_vector_t *tables; + ecs_vector_t *empty_tables; + ecs_map_t *table_indices; + + /* Handle to system (optional) */ + ecs_entity_t system; + + /* Used for sorting */ + ecs_entity_t sort_on_component; + ecs_compare_action_t compare; + ecs_vector_t *table_slices; + + /* Used for table sorting */ + ecs_entity_t rank_on_component; + ecs_rank_type_action_t group_table; + + /* Subqueries */ + ecs_query_t *parent; + ecs_vector_t *subqueries; + + /* The query kind determines how it is registered with tables */ + ecs_flags32_t flags; + + int32_t cascade_by; /* Identify CASCADE column */ + int32_t match_count; /* How often have tables been (un)matched */ + int32_t prev_match_count; /* Used to track if sorting is needed */ + bool needs_reorder; /* Whether next iteration should reorder */ +}; + +/** Keep track of how many [in] columns are active for [out] columns of OnDemand + * systems. */ +typedef struct ecs_on_demand_out_t { + ecs_entity_t system; /* Handle to system */ + int32_t count; /* Total number of times [out] columns are used */ +} ecs_on_demand_out_t; + +/** Keep track of which OnDemand systems are matched with which [in] columns */ +typedef struct ecs_on_demand_in_t { + int32_t count; /* Number of active systems with [in] column */ + ecs_vector_t *systems; /* Systems that have this column as [out] column */ +} ecs_on_demand_in_t; + +/** Types for deferred operations */ +typedef enum ecs_op_kind_t { + EcsOpNew, + EcsOpClone, + EcsOpBulkNew, + EcsOpAdd, + EcsOpRemove, + EcsOpSet, + EcsOpMut, + EcsOpModified, + EcsOpDelete, + EcsOpClear, + EcsOpEnable, + EcsOpDisable +} ecs_op_kind_t; + +typedef struct ecs_op_1_t { + ecs_entity_t entity; /* Entity id */ + void *value; /* Value (used for set / get_mut) */ + ecs_size_t size; /* Size of value */ + bool clone_value; /* Clone entity with value (used for clone) */ +} ecs_op_1_t; + +typedef struct ecs_op_n_t { + ecs_entity_t *entities; + void **bulk_data; + int32_t count; +} ecs_op_n_t; + +typedef struct ecs_op_t { + ecs_op_kind_t kind; /* Operation kind */ + ecs_entity_t scope; /* Scope of operation (for new) */ + ecs_entity_t component; /* Single component (components.count = 1) */ + ecs_entities_t components; /* Multiple components */ + union { + ecs_op_1_t _1; + ecs_op_n_t _n; + } is; +} ecs_op_t; + +/** A stage is a data structure in which delta's are stored until it is safe to + * merge those delta's with the main world stage. A stage allows flecs systems + * to arbitrarily add/remove/set components and create/delete entities while + * iterating. Additionally, worker threads have their own stage that lets them + * mutate the state of entities without requiring locks. */ +struct ecs_stage_t { + /* This points to the world pointer associated with the stage. Even though + * stages belong to the same world, when multithreaded, an application will + * receive a pointer not to the world, but to a thread. This allows for + * transparently passing the thread context without having to fallback on + * more expensive methods such as thread local storage. This world pointer + * is stored in the stage, so that it can be easily passed around when for + * example invoking callbacks, and prevents the API from passing around two + * world pointers (or constantly obtaining the real world when needed). */ + ecs_world_t *world; + + int32_t id; /* Unique id that identifies the stage */ + + /* Are operations deferred? */ + int32_t defer; + ecs_vector_t *defer_queue; + ecs_vector_t *defer_merge_queue; + + /* One-shot actions to be executed after the merge */ + ecs_vector_t *post_frame_actions; + + /* Namespacing */ + ecs_table_t *scope_table; /* Table for current scope */ + ecs_entity_t scope; /* Entity of current scope */ + + /* If a system is progressing it will set this field to its columns. This + * will be used in debug mode to verify that a system is not doing + * unanounced adding/removing of components, as this could cause + * unpredictable behavior during a merge. */ +#ifndef NDEBUG + ecs_entity_t system; + ecs_vector_t *system_columns; +#endif +}; + +typedef struct ecs_store_t { + /* Entity lookup table for (table, row) */ + ecs_sparse_t *entity_index; + + /* Table graph */ + ecs_sparse_t *tables; + ecs_table_t root; + + /* Lookup map for tables */ + ecs_map_t *table_map; +} ecs_store_t; + +/** Supporting type to store looked up or derived entity data */ +typedef struct ecs_entity_info_t { + ecs_record_t *record; /* Main stage record in entity index */ + ecs_table_t *table; /* Table. Not set if entity is empty */ + ecs_data_t *data; /* Stage-specific table columns */ + int32_t row; /* Actual row (stripped from is_watched bit) */ + bool is_watched; /* Is entity being watched */ +} ecs_entity_info_t; + +/** A type desribing a worker thread. When a system is invoked by a worker + * thread, it receives a pointer to an ecs_thread_t instead of a pointer to an + * ecs_world_t (provided by the ecs_iter_t type). When this ecs_thread_t is passed down + * into the flecs API, the API functions are able to tell whether this is an + * ecs_thread_t or an ecs_world_t by looking at the 'magic' number. This allows the + * API to transparently resolve the stage to which updates should be written, + * without requiring different API calls when working in multi threaded mode. */ +typedef struct ecs_thread_t { + int32_t magic; /* Magic number to verify thread pointer */ + ecs_world_t *world; /* Reference to world */ + ecs_stage_t *stage; /* Stage for thread */ + ecs_os_thread_t thread; /* Thread handle */ + int32_t index; /* Index of thread */ +} ecs_thread_t; + +/** Supporting type to store looked up component data in specific table */ +typedef struct ecs_column_info_t { + ecs_entity_t id; + ecs_c_info_t *ci; + int32_t column; +} ecs_column_info_t; + +/* Component monitors */ +typedef struct ecs_component_monitor_t { + bool dirty_flags[ECS_HI_COMPONENT_ID]; + ecs_vector_t *monitors[ECS_HI_COMPONENT_ID]; + bool rematch; +} ecs_component_monitor_t; + +/* fini actions */ +typedef struct ecs_action_elem_t { + ecs_fini_action_t action; + void *ctx; +} ecs_action_elem_t; + +/* Alias */ +typedef struct ecs_alias_t { + char *name; + ecs_entity_t entity; +} ecs_alias_t; + +/** The world stores and manages all ECS data. An application can have more than + * one world, but data is not shared between worlds. */ +struct ecs_world_t { + int32_t magic; /* Magic number to verify world pointer */ + void *context; /* Application context */ + ecs_vector_t *fini_actions; /* Callbacks to execute when world exits */ + + ecs_c_info_t c_info[ECS_HI_COMPONENT_ID]; /* Component callbacks & triggers */ + ecs_map_t *t_info; /* Tag triggers */ + + /* Is entity range checking enabled? */ + bool range_check_enabled; + + /* -- Data storage -- */ + + ecs_store_t store; + + + /* -- Queries -- */ + + /* Persistent queries registered with the world. Persistent queries are + * stateful and automatically matched with existing and new tables. */ + ecs_vector_t *queries; + + /* Keep track of components that were added/removed to/from monitored + * entities. Monitored entities are entities that a query has matched with + * specifically, as is the case with PARENT / CASCADE columns, FromEntity + * columns and columns matched from prefabs. + * When these entities change type, queries may have to be rematched. + * Queries register themselves as component monitors for specific components + * and when these components change they are rematched. The component + * monitors are evaluated during a merge. */ + ecs_component_monitor_t component_monitors; + + /* Parent monitors are like normal component monitors except that the + * conditions under which a parent component is flagged dirty is different. + * Parent component flags are marked dirty when an entity that is a parent + * adds or removes a CHILDOF flag. In that case, every component of that + * parent will be marked dirty. This allows column modifiers like CASCADE + * to correctly determine when the depth ranking of a table has changed. */ + ecs_component_monitor_t parent_monitors; + + + /* -- Systems -- */ + + ecs_entity_t pipeline; /* Current pipeline */ + ecs_map_t *on_activate_components; /* Trigger on activate of [in] column */ + ecs_map_t *on_enable_components; /* Trigger on enable of [in] column */ + ecs_vector_t *fini_tasks; /* Tasks to execute on ecs_fini */ + + + /* -- Lookup Indices -- */ + + ecs_map_t *type_handles; /* Handles to named types */ + + + /* -- Aliasses -- */ + + ecs_vector_t *aliases; + + + /* -- Staging -- */ + + ecs_stage_t stage; /* Main storage */ + ecs_stage_t temp_stage; /* Stage for when processing systems */ + ecs_vector_t *worker_stages; /* Stages for worker threads */ + int32_t stage_count; /* Number of stages in world */ + + + /* -- Hierarchy administration -- */ + + ecs_map_t *child_tables; /* Child tables per parent entity */ + const char *name_prefix; /* Remove prefix from C names in modules */ + + + /* -- Multithreading -- */ + + ecs_vector_t *workers; /* Worker threads */ + + ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ + ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ + ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ + int32_t workers_running; /* Number of threads running */ + int32_t workers_waiting; /* Number of workers waiting on sync */ + + + /* -- Time management -- */ + + ecs_time_t world_start_time; /* Timestamp of simulation start */ + ecs_time_t frame_start_time; /* Timestamp of frame start */ + FLECS_FLOAT fps_sleep; /* Sleep time to prevent fps overshoot */ + + + /* -- Metrics -- */ + + ecs_world_info_t stats; + + + /* -- Settings from command line arguments -- */ + + int arg_fps; + int arg_threads; + + + /* -- World lock -- */ + + ecs_os_mutex_t mutex; /* Locks the world if locking enabled */ + ecs_os_mutex_t thr_sync; /* Used to signal threads at end of frame */ + ecs_os_cond_t thr_cond; /* Used to signal threads at end of frame */ + + + /* -- Defered operation count -- */ + + int32_t new_count; + int32_t bulk_new_count; + int32_t delete_count; + int32_t clear_count; + int32_t add_count; + int32_t remove_count; + int32_t set_count; + int32_t discard_count; + + + /* -- World state -- */ + + bool quit_workers; /* Signals worker threads to quit */ + bool in_progress; /* Is world being progressed */ + bool is_merging; /* Is world currently being merged */ + bool auto_merge; /* Are stages auto-merged by ecs_progress */ + bool measure_frame_time; /* Time spent on each frame */ + bool measure_system_time; /* Time spent by each system */ + bool should_quit; /* Did a system signal that app should quit */ + bool locking_enabled; /* Lock world when in progress */ +}; + +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Core bootstrap functions +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_TYPE_DECL(component)\ +static const ecs_entity_t __##component = ecs_typeid(component);\ +ECS_VECTOR_DECL(FLECS__T##component, ecs_entity_t, 1) + +#define ECS_TYPE_IMPL(component)\ +ECS_VECTOR_IMPL(FLECS__T##component, ecs_entity_t, &__##component, 1) + +/* Bootstrap world */ +void ecs_bootstrap( + ecs_world_t *world); + +ecs_type_t ecs_bootstrap_type( + ecs_world_t *world, + ecs_entity_t entity); + +#define ecs_bootstrap_component(world, name)\ + ecs_new_component(world, ecs_typeid(name), #name, sizeof(name), ECS_ALIGNOF(name)) + +#define ecs_bootstrap_tag(world, name)\ + ecs_set(world, name, EcsName, {.value = &#name[ecs_os_strlen("Ecs")], .symbol = #name});\ + ecs_add_entity(world, name, ECS_CHILDOF | ecs_get_scope(world)) + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity API +//////////////////////////////////////////////////////////////////////////////// + +/* Mark an entity as being watched. This is used to trigger automatic rematching + * when entities used in system expressions change their components. */ +void ecs_set_watch( + ecs_world_t *world, + ecs_entity_t entity); + +/* Does one of the entity containers has specified component */ +ecs_entity_t ecs_find_in_type( + ecs_world_t *world, + ecs_type_t table_type, + ecs_entity_t component, + ecs_entity_t flags); + +/* Obtain entity info */ +bool ecs_get_info( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_info_t *info); + +void ecs_run_monitors( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_vector_t *v_dst_monitors, + int32_t dst_row, + int32_t count, + ecs_vector_t *v_src_monitors); + + +//////////////////////////////////////////////////////////////////////////////// +//// World API +//////////////////////////////////////////////////////////////////////////////// + +/* Notify systems that there is a new table, which triggers matching */ +void ecs_notify_queries_of_table( + ecs_world_t *world, + ecs_table_t *table); + +/* Get current thread-specific stage */ +ecs_stage_t *ecs_get_stage( + ecs_world_t **world_ptr); + +/* Get component callbacks */ +ecs_c_info_t *ecs_get_c_info( + ecs_world_t *world, + ecs_entity_t component); + +/* Get or create component callbacks */ +ecs_c_info_t * ecs_get_or_create_c_info( + ecs_world_t *world, + ecs_entity_t component); + +void ecs_eval_component_monitors( + ecs_world_t *world); + +void ecs_component_monitor_mark( + ecs_component_monitor_t *mon, + ecs_entity_t component); + +void ecs_component_monitor_register( + ecs_component_monitor_t *mon, + ecs_entity_t component, + ecs_query_t *query); + +void ecs_notify_tables( + ecs_world_t *world, + ecs_table_event_t *event); + +void ecs_notify_queries( + ecs_world_t *world, + ecs_query_event_t *event); + + +//////////////////////////////////////////////////////////////////////////////// +//// Stage API +//////////////////////////////////////////////////////////////////////////////// + +/* Initialize stage data structures */ +void ecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Deinitialize stage */ +void ecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Merge stage with main stage */ +void ecs_stage_merge( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Post-frame merge actions */ +void ecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Begin defer for stage */ +bool ecs_stage_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage); + +bool ecs_stage_defer_end( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Delete table from stage */ +void ecs_delete_table( + ecs_world_t *world, + ecs_table_t *table); + +//////////////////////////////////////////////////////////////////////////////// +//// Defer API +//////////////////////////////////////////////////////////////////////////////// + +bool ecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage); + +bool ecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component); + +bool ecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entities_t *components); + +bool ecs_defer_clone( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value); + +bool ecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_entities_t *components, + void **component_data, + const ecs_entity_t **ids_out); + +bool ecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool ecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool ecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable); + +bool ecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entities_t *components); + +bool ecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entities_t *components); + +bool ecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added); + +bool ecs_defer_flush( + ecs_world_t *world, + ecs_stage_t *stage); + +//////////////////////////////////////////////////////////////////////////////// +//// Type API +//////////////////////////////////////////////////////////////////////////////// + +/* Merge add/remove types */ +ecs_type_t ecs_type_merge_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_type_t cur_id, + ecs_type_t to_add_id, + ecs_type_t to_remove_id); + +/* Test if type_id_1 contains type_id_2 */ +ecs_entity_t ecs_type_contains( + ecs_world_t *world, + ecs_type_t type_id_1, + ecs_type_t type_id_2, + bool match_all, + bool match_prefab); + +/* Add component to type */ +ecs_type_t ecs_type_add_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_type_t type, + ecs_entity_t component); + +/* Find entity in prefabs of type */ +ecs_entity_t ecs_find_entity_in_prefabs( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type, + ecs_entity_t component, + ecs_entity_t previous); + +void ecs_get_column_info( + ecs_world_t *world, + ecs_table_t *table, + ecs_entities_t *components, + ecs_column_info_t *cinfo, + bool get_all); + +void ecs_run_add_actions( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_entities_t *added, + bool get_all, + bool run_on_set); + +void ecs_run_remove_actions( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_entities_t *removed, + bool get_all); + +void ecs_run_set_systems( + ecs_world_t *world, + ecs_entities_t *components, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + bool set_all); + + +//////////////////////////////////////////////////////////////////////////////// +//// Table API +//////////////////////////////////////////////////////////////////////////////// + +/** Find or create table for a set of components */ +ecs_table_t* ecs_table_find_or_create( + ecs_world_t *world, + ecs_entities_t *type); + +/* Get table data */ +ecs_data_t *ecs_table_get_data( + ecs_table_t *table); + +/* Get or create data */ +ecs_data_t *ecs_table_get_or_create_data( + ecs_table_t *table); + +/* Initialize columns for data */ +ecs_data_t* ecs_init_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *result); + +/* Activates / deactivates table for systems. A deactivated table will not be + * evaluated when the system is invoked. Tables automatically get activated / + * deactivated when they become non-empty / empty. + * + * If a query is provided, the table will only be activated / deactivated for + * that query. */ +void ecs_table_activate( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + bool activate); + +/* Clear all entities from a table. */ +void ecs_table_clear( + ecs_world_t *world, + ecs_table_t *table); + +/* Reset a table to its initial state */ +void ecs_table_reset( + ecs_world_t *world, + ecs_table_t *table); + +/* Clear all entities from the table. Do not invoke OnRemove systems */ +void ecs_table_clear_silent( + ecs_world_t *world, + ecs_table_t *table); + +/* Clear table data. Don't call OnRemove handlers. */ +void ecs_table_clear_data( + ecs_table_t *table, + ecs_data_t *data); + +/* Return number of entities in data */ +int32_t ecs_table_data_count( + ecs_data_t *data); + +/* Add a new entry to the table for the specified entity */ +int32_t ecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t entity, + ecs_record_t *record, + bool construct); + +/* Delete an entity from the table. */ +void ecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t index, + bool destruct); + +/* Move a row from one table to another */ +void ecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index); + +/* Grow table with specified number of records. Populate table with entities, + * starting from specified entity id. */ +int32_t ecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t count, + const ecs_entity_t *ids); + +/* Set table to a fixed size. Useful for preallocating memory in advance. */ +void ecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t count); + +/* Match table with filter */ +bool ecs_table_match_filter( + ecs_world_t *world, + ecs_table_t *table, + const ecs_filter_t *filter); + +/* Get dirty state for table columns */ +int32_t* ecs_table_get_dirty_state( + ecs_table_t *table); + +/* Get monitor for monitoring table changes */ +int32_t* ecs_table_get_monitor( + ecs_table_t *table); + +/* Initialize root table */ +void ecs_init_root_table( + ecs_world_t *world); + +/* Unset components in table */ +void ecs_table_unset( + ecs_world_t *world, + ecs_table_t *table); + +/* Free table */ +void ecs_table_free( + ecs_world_t *world, + ecs_table_t *table); + +/* Merge table data */ +void ecs_table_merge_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* Replace data */ +void ecs_table_replace_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* Merge data of one table into another table */ +ecs_data_t* ecs_table_merge( + ecs_world_t *world, + ecs_table_t *new_table, + ecs_table_t *old_table, + ecs_data_t *new_data, + ecs_data_t *old_data); + +void ecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2); + +ecs_table_t *ecs_table_traverse_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_entities_t *to_add, + ecs_entities_t *added); + +ecs_table_t *ecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_entities_t *to_remove, + ecs_entities_t *removed); + +void ecs_table_mark_dirty( + ecs_table_t *table, + ecs_entity_t component); + +const EcsComponent* ecs_component_from_id( + ecs_world_t *world, + ecs_entity_t e); + +int32_t ecs_table_switch_from_case( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t add); + +void ecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t *event); + +void ecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table); + +//////////////////////////////////////////////////////////////////////////////// +//// Query API +//////////////////////////////////////////////////////////////////////////////// + +void ecs_query_set_iter( + ecs_world_t *world, + ecs_query_t *query, + ecs_iter_t *it, + int32_t table_index, + int32_t row, + int32_t count); + +void ecs_query_rematch( + ecs_world_t *world, + ecs_query_t *query); + +void ecs_run_monitor( + ecs_world_t *world, + ecs_matched_query_t *monitor, + ecs_entities_t *components, + int32_t row, + int32_t count, + ecs_entity_t *entities); + +bool ecs_query_match( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + ecs_match_failure_t *failure_info); + +void ecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event); + +//////////////////////////////////////////////////////////////////////////////// +//// Signature API +//////////////////////////////////////////////////////////////////////////////// + +/* Check if all non-table column constraints are met */ +bool ecs_sig_check_constraints( + ecs_world_t *world, + ecs_sig_t *sig); + + +//////////////////////////////////////////////////////////////////////////////// +//// Time API +//////////////////////////////////////////////////////////////////////////////// + +void ecs_os_time_setup(void); + +uint64_t ecs_os_time_now(void); + +void ecs_os_time_sleep( + int32_t sec, + int32_t nanosec); + +/* Increase or reset timer resolution (Windows only) */ +FLECS_API +void ecs_increase_timer_resolution( + bool enable); + +//////////////////////////////////////////////////////////////////////////////// +//// Utilities +//////////////////////////////////////////////////////////////////////////////// + +void ecs_hash( + const void *data, + ecs_size_t length, + uint64_t *result); + +/* Convert 64 bit signed integer to 16 bit */ +int8_t ecs_to_i8( + int64_t v); + +/* Convert 64 bit signed integer to 16 bit */ +int16_t ecs_to_i16( + int64_t v); + +/* Convert 64 bit unsigned integer to 32 bit */ +uint32_t ecs_to_u32( + uint64_t v); + +/* Convert signed integer to size_t */ +size_t ecs_to_size_t( + int64_t size); + +/* Convert size_t to ecs_size_t */ +ecs_size_t ecs_from_size_t( + size_t size); + +/* Get next power of 2 */ +int32_t ecs_next_pow_of_2( + int32_t n); + +/* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the + * entity index */ +ecs_record_t ecs_to_row( + uint64_t value); + +/* Get 64bit integer from ecs_record_t */ +uint64_t ecs_from_row( + ecs_record_t record); + +/* Get actual row from record row */ +int32_t ecs_record_to_row( + int32_t row, + bool *is_watched_out); + +/* Convert actual row to record row */ +int32_t ecs_row_to_record( + int32_t row, + bool is_watched); + +/* Convert type to entity array */ +ecs_entities_t ecs_type_to_entities( + ecs_type_t type); + +/* Convert a symbol name to an entity name by removing the prefix */ +const char* ecs_name_from_symbol( + ecs_world_t *world, + const char *type_name); + +/* Lookup an entity by name with a specific id */ +ecs_entity_t ecs_lookup_w_id( + ecs_world_t *world, + ecs_entity_t e, + const char *name); + +/* Set entity name with symbol */ +void ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t e, + const char *name); + +/* Utility that print a descriptive error string*/ +//void ecs_print_error_string(const char *error_description, const char* signature, const char* system_id, const char* component_id); +//void ecs_print_error_string(const char* signature, const char *system_id, const char *error_description, const char *component_id); + +/* Utility that parses system signature */ +int ecs_parse_expr( + ecs_world_t *world, + const char *name, + const char *sig, + ecs_parse_action_t action, + void *ctx); + +#define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__) +void _assert_func( + bool cond, + const char *cond_str, + const char *file, + int32_t line, + const char *func); + +#endif + +static +char *ecs_vasprintf( + const char *fmt, + va_list args) +{ + ecs_size_t size = 0; + char *result = NULL; + va_list tmpa; + + va_copy(tmpa, args); + + size = vsnprintf(result, ecs_to_size_t(size), fmt, tmpa); + + va_end(tmpa); + + if ((int32_t)size < 0) { + return NULL; + } + + result = (char *) ecs_os_malloc(size + 1); + + if (!result) { + return NULL; + } + + ecs_os_vsprintf(result, fmt, args); + + return result; +} + +static +char* ecs_colorize( + char *msg) +{ + ecs_strbuf_t buff = ECS_STRBUF_INIT; + char *ptr, ch, prev = '\0'; + bool isNum = false; + char isStr = '\0'; + bool isVar = false; + bool overrideColor = false; + bool autoColor = true; + bool dontAppend = false; + bool use_colors = true; + + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; + + if (!overrideColor) { + if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + isNum = false; + } + if (isStr && (isStr == ch) && prev != '\\') { + isStr = '\0'; + } else if (((ch == '\'') || (ch == '"')) && !isStr && + !isalpha(prev) && (prev != '\\')) + { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN); + isStr = ch; + } + + if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || + (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && + !isalpha(prev) && !isdigit(prev) && (prev != '_') && + (prev != '.')) + { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_GREEN); + isNum = true; + } + + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + isVar = false; + } + + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN); + isVar = true; + } + } + + if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { + bool isColor = true; + overrideColor = true; + + /* Custom colors */ + if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { + autoColor = false; + } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_GREEN); + } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_RED); + } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_BLUE); + } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_MAGENTA); + } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN); + } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_YELLOW); + } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_GREY); + } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_BOLD); + } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { + overrideColor = false; + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } else { + isColor = false; + overrideColor = false; + } + + if (isColor) { + ptr += 2; + while ((ch = *ptr) != ']') ptr ++; + dontAppend = true; + } + if (!autoColor) { + overrideColor = true; + } + } + + if (ch == '\n') { + if (isNum || isStr || isVar || overrideColor) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + overrideColor = false; + isNum = false; + isStr = false; + isVar = false; + } + } + + if (!dontAppend) { + ecs_strbuf_appendstrn(&buff, ptr, 1); + } + + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } + } + + prev = ch; + } + + if (isNum || isStr || isVar || overrideColor) { + if (use_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } + + return ecs_strbuf_get(&buff); +} + +static int trace_indent = 0; +static int trace_level = 0; + +static +void ecs_log_print( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list valist) +{ + (void)level; + (void)line; + + if (level > trace_level) { + return; + } + + /* Massage filename so it doesn't take up too much space */ + char filebuff[256]; + ecs_os_strcpy(filebuff, file); + file = filebuff; + char *file_ptr = strrchr(file, '/'); + if (file_ptr) { + file = file_ptr + 1; + } + + /* Extension is likely the same for all files */ + file_ptr = strrchr(file, '.'); + if (file_ptr) { + *file_ptr = '\0'; + } + + char indent[32]; + int i; + for (i = 0; i < trace_indent; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; + } + indent[i * 2] = '\0'; + + char *msg = ecs_vasprintf(fmt, valist); + char *color_msg = ecs_colorize(msg); + + if (level >= 0) { + ecs_os_log("%sinfo%s: %s%s%s%s", + ECS_MAGENTA, ECS_NORMAL, ECS_GREY, indent, ECS_NORMAL, color_msg); + } else if (level == -2) { + ecs_os_warn("%swarn%s: %s%s%s%s", + ECS_YELLOW, ECS_NORMAL, ECS_GREY, indent, ECS_NORMAL, color_msg); + } else if (level <= -2) { + ecs_os_err("%serr %s: %s%s%s%s", + ECS_RED, ECS_NORMAL, ECS_GREY, indent, ECS_NORMAL, color_msg); + } + + ecs_os_free(color_msg); + ecs_os_free(msg); +} + +void _ecs_trace( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list valist; + va_start(valist, fmt); + + ecs_log_print(level, file, line, fmt, valist); +} + +void _ecs_warn( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list valist; + va_start(valist, fmt); + + ecs_log_print(-2, file, line, fmt, valist); +} + +void _ecs_err( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list valist; + va_start(valist, fmt); + + ecs_log_print(-3, file, line, fmt, valist); +} + +void ecs_log_push(void) { + trace_indent ++; +} + +void ecs_log_pop(void) { + trace_indent --; +} + +void ecs_tracing_enable( + int level) +{ + trace_level = level; +} + +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + if (trace_level >= -2) { + va_list valist; + va_start(valist, fmt); + char *msg = ecs_vasprintf(fmt, valist); + + ecs_os_err("%s:%d: error: %s", name, column + 1, msg); + ecs_os_err(" %s", expr); + ecs_os_err(" %*s^", column, ""); + + ecs_os_free(msg); + } + + ecs_os_abort(); +} + +void _ecs_abort( + int32_t error_code, + const char *param, + const char *file, + int32_t line) +{ + if (param) { + ecs_err("abort %s:%d: %s (%s)", + file, line, ecs_strerror(error_code), param); + } else { + ecs_err("abort %s:%d: %s", file, line, ecs_strerror(error_code)); + } + + ecs_os_abort(); +} + +void _ecs_assert( + bool condition, + int32_t error_code, + const char *param, + const char *condition_str, + const char *file, + int32_t line) +{ + if (!condition) { + if (param) { + ecs_err("assert(%s) %s:%d: %s (%s)", + condition_str, file, line, ecs_strerror(error_code), param); + } else { + ecs_err("assert(%s) %s:%d: %s", + condition_str, file, line, ecs_strerror(error_code)); + } + + ecs_os_abort(); + } +} + +const char* ecs_strerror( + int32_t error_code) +{ + switch (error_code) { + case ECS_INVALID_ENTITY: + return "invalid entity"; + case ECS_INVALID_PARAMETER: + return "invalid parameters"; + case ECS_INVALID_COMPONENT_ID: + return "invalid component id"; + case ECS_INVALID_TYPE_EXPRESSION: + return "invalid type expression"; + case ECS_INVALID_SIGNATURE: + return "invalid system signature"; + case ECS_INVALID_EXPRESSION: + return "invalid type expression/signature"; + case ECS_MISSING_SYSTEM_CONTEXT: + return "missing system context"; + case ECS_UNKNOWN_COMPONENT_ID: + return "unknown component id"; + case ECS_UNKNOWN_TYPE_ID: + return "unknown type id"; + case ECS_TYPE_NOT_AN_ENTITY: + return "type contains more than one entity"; + case ECS_NOT_A_COMPONENT: + return "handle is not a component"; + case ECS_INTERNAL_ERROR: + return "internal error"; + case ECS_MORE_THAN_ONE_PREFAB: + return "more than one prefab added to entity"; + case ECS_ALREADY_DEFINED: + return "entity has already been defined"; + case ECS_INVALID_COMPONENT_SIZE: + return "the specified size does not match the component"; + case ECS_OUT_OF_MEMORY: + return "out of memory"; + case ECS_MODULE_UNDEFINED: + return "module is undefined"; + case ECS_COLUMN_INDEX_OUT_OF_RANGE: + return "column index out of range"; + case ECS_COLUMN_IS_NOT_SHARED: + return "column is not shared"; + case ECS_COLUMN_IS_SHARED: + return "column is shared"; + case ECS_COLUMN_HAS_NO_DATA: + return "column has no data"; + case ECS_COLUMN_TYPE_MISMATCH: + return "column retrieved with mismatching type"; + case ECS_INVALID_WHILE_MERGING: + return "operation is invalid while merging"; + case ECS_INVALID_WHILE_ITERATING: + return "operation is invalid while iterating"; + case ECS_INVALID_FROM_WORKER: + return "operation is invalid from worker thread"; + case ECS_UNRESOLVED_IDENTIFIER: + return "unresolved identifier"; + case ECS_OUT_OF_RANGE: + return "index is out of range"; + case ECS_COLUMN_IS_NOT_SET: + return "column is not set (use ecs_column_test for optional columns)"; + case ECS_UNRESOLVED_REFERENCE: + return "unresolved reference for system"; + case ECS_THREAD_ERROR: + return "failed to create thread"; + case ECS_MISSING_OS_API: + return "missing implementation for OS API function"; + case ECS_TYPE_TOO_LARGE: + return "type contains too many entities"; + case ECS_INVALID_PREFAB_CHILD_TYPE: + return "a prefab child type must have at least one INSTANCEOF element"; + case ECS_UNSUPPORTED: + return "operation is unsupported"; + case ECS_NO_OUT_COLUMNS: + return "on demand system has no out columns"; + case ECS_COLUMN_ACCESS_VIOLATION: + return "invalid access to readonly column (use const)"; + case ECS_DESERIALIZE_COMPONENT_ID_CONFLICT: + return "serialized data contains conflicting component id"; + case ECS_DESERIALIZE_COMPONENT_SIZE_CONFLICT: + return "serialized data contains conflicting component size"; + case ECS_DESERIALIZE_FORMAT_ERROR: + return "serialized data has invalid format"; + case ECS_INVALID_REACTIVE_SIGNATURE: + return "signature is not valid for reactive system (must contain at least one ANY column)"; + case ECS_INCONSISTENT_COMPONENT_NAME: + return "component redefined with a different name"; + case ECS_TYPE_CONSTRAINT_VIOLATION: + return "type constraint violated"; + case ECS_COMPONENT_NOT_REGISTERED: + return "component is not registered"; + case ECS_INCONSISTENT_COMPONENT_ID: + return "component redefined with a different id"; + case ECS_INVALID_CASE: + return "case not supported for type"; + case ECS_COMPONENT_NAME_IN_USE: + return "component name is already in use"; + case ECS_INCONSISTENT_NAME: + return "entity redefined with different name"; + case ECS_INCONSISTENT_COMPONENT_ACTION: + return "registered mismatching component action"; + case ECS_INVALID_OPERATION: + return "invalid operation"; + } + + return "unknown error code"; +} + +ecs_data_t* ecs_init_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *result) +{ + ecs_type_t type = table->type; + int32_t i, + count = table->column_count, + sw_count = table->sw_column_count, + bs_count = table->bs_column_count; + + /* Root tables don't have columns */ + if (!count && !sw_count && !bs_count) { + result->columns = NULL; + return result; + } + + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + if (count && !sw_count) { + result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * count); + } else if (count || sw_count) { + /* If a table has switch columns, store vector with the case values + * as a regular column, so it's easier to access for systems. To + * enable this, we need to allocate more space. */ + int32_t type_count = ecs_vector_count(type); + result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * type_count); + } + + if (count) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Is the column a component? */ + const EcsComponent *component = ecs_component_from_id(world, e); + if (component) { + /* Is the component associated wit a (non-empty) type? */ + if (component->size) { + /* This is a regular component column */ + result->columns[i].size = ecs_to_i16(component->size); + result->columns[i].alignment = ecs_to_i16(component->alignment); + } else { + /* This is a tag */ + } + } else { + /* This is an entity that was added to the type */ + } + } + } + + if (sw_count) { + int32_t sw_offset = table->sw_column_offset; + result->sw_columns = ecs_os_calloc(ECS_SIZEOF(ecs_sw_column_t) * sw_count); + + for (i = 0; i < sw_count; i ++) { + ecs_entity_t e = entities[i + sw_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t sw_type = type_ptr->normalized; + + ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t); + int32_t sw_array_count = ecs_vector_count(sw_type); + + ecs_switch_t *sw = ecs_switch_new( + sw_array[0], + sw_array[sw_array_count - 1], + 0); + result->sw_columns[i].data = sw; + result->sw_columns[i].type = sw_type; + + int32_t column_id = i + table->sw_column_offset; + result->columns[column_id].data = ecs_switch_values(sw); + result->columns[column_id].size = sizeof(ecs_entity_t); + result->columns[column_id].alignment = ECS_ALIGNOF(ecs_entity_t); + } + } + + if (bs_count) { + result->bs_columns = ecs_os_calloc(ECS_SIZEOF(ecs_bs_column_t) * bs_count); + for (i = 0; i < bs_count; i ++) { + ecs_bitset_init(&result->bs_columns[i].data); + } + } + + return result; +} + +static +ecs_flags32_t get_component_action_flags( + ecs_c_info_t *c_info) +{ + ecs_flags32_t flags = 0; + + if (c_info->lifecycle.ctor) { + flags |= EcsTableHasCtors; + } + if (c_info->lifecycle.dtor) { + flags |= EcsTableHasDtors; + } + if (c_info->lifecycle.copy) { + flags |= EcsTableHasCopy; + } + if (c_info->lifecycle.move) { + flags |= EcsTableHasMove; + } + if (c_info->on_add) { + flags |= EcsTableHasOnAdd; + } + if (c_info->on_remove) { + flags |= EcsTableHasOnRemove; + } + + return flags; +} + +/* Check if table has instance of component, including traits */ +static +bool has_component( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t component) +{ + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + + for (i = 0; i < count; i ++) { + if (component == ecs_get_typeid(world, entities[i])) { + return true; + } + } + + return false; +} + +static +void notify_component_info( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_type_t table_type = table->type; + if (!component || has_component(world, table_type, component)){ + int32_t column_count = ecs_vector_count(table_type); + ecs_assert(!component || column_count != 0, ECS_INTERNAL_ERROR, NULL); + + if (!column_count) { + return; + } + + if (!table->c_info) { + table->c_info = ecs_os_calloc( + ECS_SIZEOF(ecs_c_info_t*) * column_count); + } + + /* Reset lifecycle flags before recomputing */ + table->flags &= ~EcsTableHasLifecycle; + + /* Recompute lifecycle flags */ + ecs_entity_t *array = ecs_vector_first(table_type, ecs_entity_t); + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_entity_t c = ecs_get_typeid(world, array[i]); + if (!c) { + continue; + } + + ecs_c_info_t *c_info = ecs_get_c_info(world, c); + if (c_info) { + ecs_flags32_t flags = get_component_action_flags(c_info); + table->flags |= flags; + } + + /* Store pointer to c_info for fast access */ + table->c_info[i] = c_info; + } + } +} + +static +void run_un_set_handlers( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + int32_t count = ecs_vector_count(data->entities); + if (count) { + ecs_run_monitors(world, table, table->un_set_all, 0, count, NULL); + } +} + +static +int compare_matched_query( + const void *ptr1, + const void *ptr2) +{ + const ecs_matched_query_t *m1 = ptr1; + const ecs_matched_query_t *m2 = ptr2; + ecs_query_t *q1 = m1->query; + ecs_query_t *q2 = m2->query; + ecs_assert(q1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(q2 != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t s1 = q1->system; + ecs_entity_t s2 = q2->system; + ecs_assert(s1 != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(s2 != 0, ECS_INTERNAL_ERROR, NULL); + + return (s1 > s2) - (s1 < s2); +} + +static +void add_monitor( + ecs_vector_t **array, + ecs_query_t *query, + int32_t matched_table_index) +{ + /* Add the system to a list that contains all OnSet systems matched with + * this table. This makes it easy to get the list of systems that need to be + * executed when all components are set, like when new_w_data is used */ + ecs_matched_query_t *m = ecs_vector_add(array, ecs_matched_query_t); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + m->query = query; + m->matched_table_index = matched_table_index; + + /* Sort the system list so that it is easy to get the difference OnSet + * OnSet systems between two tables. */ + qsort( + ecs_vector_first(*array, ecs_matched_query_t), + ecs_to_size_t(ecs_vector_count(*array)), + ECS_SIZEOF(ecs_matched_query_t), + compare_matched_query); +} + +/* This function is called when a query is matched with a table. A table keeps + * a list of tables that match so that they can be notified when the table + * becomes empty / non-empty. */ +static +void register_monitor( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + /* First check if system is already registered as monitor. It is possible + * the query just wants to update the matched_table_index (for example, if + * query tables got reordered) */ + ecs_vector_each(table->monitors, ecs_matched_query_t, m, { + if (m->query == query) { + m->matched_table_index = matched_table_index; + return; + } + }); + + add_monitor(&table->monitors, query, matched_table_index); + +#ifndef NDEBUG + char *str = ecs_type_str(world, table->type); + ecs_trace_2("monitor #[green]%s#[reset] registered with table #[red]%s", + ecs_get_name(world, query->system), str); + ecs_os_free(str); +#endif +} + +static +bool is_override( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t comp) +{ + if (!(table->flags & EcsTableHasBase)) { + return false; + } + + ecs_type_t type = table->type; + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = entities[i]; + if (ECS_HAS_ROLE(e, INSTANCEOF)) { + if (ecs_has_entity(world, e & ECS_COMPONENT_MASK, comp)) { + return true; + } + } else { + /* ECS_INSTANCEOF will always appear at the end of a type */ + return false; + } + } + + return false; +} + +static +void register_on_set( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + + if (table->column_count) { + if (!table->on_set) { + table->on_set = + ecs_os_calloc(ECS_SIZEOF(ecs_vector_t) * table->column_count); + } + + /* Get the matched table which holds the list of actual components */ + ecs_matched_table_t *matched_table = ecs_vector_get( + query->tables, ecs_matched_table_t, matched_table_index); + + /* Keep track of whether query matches overrides. When a component is + * removed, diffing these arrays between the source and detination + * tables gives the list of OnSet systems to run, after exposing the + * component that was overridden. */ + bool match_override = false; + + /* Add system to each matched column. This makes it easy to get the list + * of systems when setting a single component. */ + ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, + ecs_sig_column_t); + int32_t i, count = ecs_vector_count(query->sig.columns); + for (i = 0; i < count; i ++) { + ecs_sig_column_t *column = &columns[i]; + ecs_sig_oper_kind_t oper_kind = column->oper_kind; + ecs_sig_from_kind_t from_kind = column->from_kind; + + if ((from_kind != EcsFromAny && from_kind != EcsFromOwned) || + (oper_kind != EcsOperAnd && oper_kind != EcsOperOptional)) + { + continue; + } + + ecs_entity_t comp = matched_table->iter_data.components[i]; + int32_t index = ecs_type_index_of(table->type, comp); + if (index == -1) { + continue; + } + + if (index >= table->column_count) { + continue; + } + + ecs_vector_t *set_c = table->on_set[index]; + ecs_matched_query_t *m = ecs_vector_add(&set_c, ecs_matched_query_t); + m->query = query; + m->matched_table_index = matched_table_index; + table->on_set[index] = set_c; + + match_override |= is_override(world, table, comp); + } + + if (match_override) { + add_monitor(&table->on_set_override, query, matched_table_index); + } + } + + add_monitor(&table->on_set_all, query, matched_table_index); +} + +static +void register_un_set( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + add_monitor(&table->un_set_all, query, matched_table_index); +} + +/* -- Private functions -- */ + +/* If table goes from 0 to >0 entities or from >0 entities to 0 entities notify + * queries. This allows systems associated with queries to move inactive tables + * out of the main loop. */ +void ecs_table_activate( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + bool activate) +{ + if (query) { + ecs_query_notify(world, query, &(ecs_query_event_t) { + .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, + .table = table + }); + } else { + ecs_vector_t *queries = table->queries; + ecs_query_t **buffer = ecs_vector_first(queries, ecs_query_t*); + int32_t i, count = ecs_vector_count(queries); + + for (i = 0; i < count; i ++) { + ecs_query_notify(world, buffer[i], &(ecs_query_event_t) { + .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, + .table = table + }); + } + } +} + +/* This function is called when a query is matched with a table. A table keeps + * a list of tables that match so that they can be notified when the table + * becomes empty / non-empty. */ +static +void register_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + /* Register system with the table */ + if (!(query->flags & EcsQueryNoActivation)) { +#ifndef NDEBUG + /* Sanity check if query has already been added */ + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + ecs_assert(*q != query, ECS_INTERNAL_ERROR, NULL); + } +#endif + + ecs_query_t **q = ecs_vector_add(&table->queries, ecs_query_t*); + if (q) *q = query; + + ecs_data_t *data = ecs_table_get_data(table); + if (data && ecs_vector_count(data->entities)) { + ecs_table_activate(world, table, query, true); + } + } + + /* Register the query as a monitor */ + if (query->flags & EcsQueryMonitor) { + table->flags |= EcsTableHasMonitors; + register_monitor(world, table, query, matched_table_index); + } + + /* Register the query as an on_set system */ + if (query->flags & EcsQueryOnSet) { + register_on_set(world, table, query, matched_table_index); + } + + /* Register the query as an un_set system */ + if (query->flags & EcsQueryUnSet) { + register_un_set(world, table, query, matched_table_index); + } +} + +/* This function is called when a query is unmatched with a table. This can + * happen for queries that have shared components expressions in their signature + * and those shared components changed (for example, a base removed a comp). */ +static +void unregister_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query) +{ + (void)world; + + if (!(query->flags & EcsQueryNoActivation)) { + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + if (*q == query) { + break; + } + } + + /* Query must have been registered with table */ + ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); + + /* Remove query */ + ecs_vector_remove_index(table->queries, ecs_query_t*, i); + } +} + +static +ecs_data_t* get_data_intern( + ecs_table_t *table, + bool create) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = table->data; + if (data) { + return data; + } + + if (!data && !create) { + return NULL; + } + + return table->data = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); +} + +ecs_data_t* ecs_table_get_data( + ecs_table_t *table) +{ + return get_data_intern(table, false); +} + +ecs_data_t* ecs_table_get_or_create_data( + ecs_table_t *table) +{ + return get_data_intern(table, true); +} + +static +void ctor_component( + ecs_world_t * world, + ecs_c_info_t * cdata, + ecs_column_t * column, + ecs_entity_t * entities, + int32_t row, + int32_t count) +{ + /* A new component is constructed */ + ecs_xtor_t ctor; + if (cdata && (ctor = cdata->lifecycle.ctor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; + + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + + ctor(world, cdata->component, entities, ptr, + ecs_to_size_t(size), count, ctx); + } +} + +static +void dtor_component( + ecs_world_t * world, + ecs_c_info_t * cdata, + ecs_column_t * column, + ecs_entity_t * entities, + int32_t row, + int32_t count) +{ + /* An old component is destructed */ + ecs_xtor_t dtor; + if (cdata && (dtor = cdata->lifecycle.dtor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; + + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + dtor(world, cdata->component, entities, ptr, + ecs_to_size_t(size), count, ctx); + } +} + +static +void dtor_all_components( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count) +{ + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t column_count = table->column_count; + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &data->columns[i]; + dtor_component( + world, table->c_info[i], column, entities, row, + count); + } +} + +static +void run_remove_actions( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + bool dtor_only) +{ + if (count) { + if (!dtor_only) { + ecs_run_monitors(world, table, NULL, row, count, table->un_set_all); + } + + dtor_all_components(world, table, data, row, count); + } +} + +void ecs_table_clear_data( + ecs_table_t * table, + ecs_data_t * data) +{ + if (!data) { + return; + } + + ecs_column_t *columns = data->columns; + if (columns) { + int32_t c, column_count = table->column_count; + for (c = 0; c < column_count; c ++) { + ecs_vector_free(columns[c].data); + } + ecs_os_free(columns); + data->columns = NULL; + } + + ecs_sw_column_t *sw_columns = data->sw_columns; + if (sw_columns) { + int32_t c, column_count = table->sw_column_count; + for (c = 0; c < column_count; c ++) { + ecs_switch_free(sw_columns[c].data); + } + ecs_os_free(sw_columns); + data->sw_columns = NULL; + } + + ecs_bs_column_t *bs_columns = data->bs_columns; + if (bs_columns) { + int32_t c, column_count = table->bs_column_count; + for (c = 0; c < column_count; c ++) { + ecs_bitset_deinit(&bs_columns[c].data); + } + ecs_os_free(bs_columns); + data->bs_columns = NULL; + } + + ecs_vector_free(data->entities); + ecs_vector_free(data->record_ptrs); + + data->entities = NULL; + data->record_ptrs = NULL; +} + +/* Clear columns. Deactivate table in systems if necessary, but do not invoke + * OnRemove handlers. This is typically used when restoring a table to a + * previous state. */ +void ecs_table_clear_silent( + ecs_world_t * world, + ecs_table_t * table) +{ + ecs_data_t *data = ecs_table_get_data(table); + if (!data) { + return; + } + + int32_t count = ecs_vector_count(data->entities); + + ecs_table_clear_data(table, table->data); + + if (count) { + ecs_table_activate(world, table, 0, false); + } +} + +/* Delete all entities in table, invoke OnRemove handlers. This function is used + * when an application invokes delete_w_filter. Use ecs_table_clear_silent, as + * the table may have to be deactivated with systems. */ +void ecs_table_clear( + ecs_world_t * world, + ecs_table_t * table) +{ + ecs_data_t *data = ecs_table_get_data(table); + if (data) { + run_remove_actions( + world, table, data, 0, ecs_table_data_count(data), false); + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t i, count = ecs_vector_count(data->entities); + for(i = 0; i < count; i ++) { + ecs_eis_delete(world, entities[i]); + } + } + + ecs_table_clear_silent(world, table); +} + +/* Unset all components in table. This function is called before a table is + * deleted, and invokes all UnSet handlers, if any */ +void ecs_table_unset( + ecs_world_t * world, + ecs_table_t * table) +{ + (void)world; + ecs_data_t *data = ecs_table_get_data(table); + if (data) { + run_un_set_handlers(world, table, data); + } +} + +/* Free table resources. Do not invoke handlers and do not activate/deactivate + * table with systems. This function is used when the world is freed. */ +void ecs_table_free( + ecs_world_t * world, + ecs_table_t * table) +{ + (void)world; + ecs_data_t *data = ecs_table_get_data(table); + if (data) { + run_remove_actions( + world, table, data, 0, ecs_table_data_count(data), false); + } + + ecs_table_clear_data(table, table->data); + ecs_table_clear_edges(world, table); + + ecs_os_free(table->lo_edges); + ecs_map_free(table->hi_edges); + ecs_vector_free(table->queries); + ecs_vector_free((ecs_vector_t*)table->type); + ecs_os_free(table->dirty_state); + ecs_vector_free(table->monitors); + ecs_vector_free(table->on_set_all); + ecs_vector_free(table->on_set_override); + ecs_vector_free(table->un_set_all); + + if (table->c_info) { + ecs_os_free(table->c_info); + } + + if (table->on_set) { + int32_t i; + for (i = 0; i < table->column_count; i ++) { + ecs_vector_free(table->on_set[i]); + } + ecs_os_free(table->on_set); + } + + table->id = 0; + + ecs_os_free(table->data); +} + +/* Reset a table to its initial state. */ +void ecs_table_reset( + ecs_world_t * world, + ecs_table_t * table) +{ + (void)world; + ecs_os_free(table->lo_edges); + ecs_map_free(table->hi_edges); + table->lo_edges = NULL; + table->hi_edges = NULL; +} + +static +void mark_table_dirty( + ecs_table_t *table, + int32_t index) +{ + if (table->dirty_state) { + table->dirty_state[index] ++; + } +} + +void ecs_table_mark_dirty( + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (table->dirty_state) { + int32_t index = ecs_type_index_of(table->type, component); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + table->dirty_state[index] ++; + } +} + +static +void move_switch_columns( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index, + int32_t count) +{ + int32_t i_old = 0, old_column_count = old_table->sw_column_count; + int32_t i_new = 0, new_column_count = new_table->sw_column_count; + + if (!old_column_count || !new_column_count) { + return; + } + + ecs_sw_column_t *old_columns = old_data->sw_columns; + ecs_sw_column_t *new_columns = new_data->sw_columns; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t offset_new = new_table->sw_column_offset; + int32_t offset_old = old_table->sw_column_offset; + + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new + offset_new]; + ecs_entity_t old_component = old_components[i_old + offset_old]; + + if (new_component == old_component) { + ecs_switch_t *old_switch = old_columns[i_old].data; + ecs_switch_t *new_switch = new_columns[i_new].data; + + ecs_switch_ensure(new_switch, new_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = ecs_switch_get(old_switch, old_index + i); + ecs_switch_set(new_switch, new_index + i, value); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +static +void move_bitset_columns( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index, + int32_t count) +{ + int32_t i_old = 0, old_column_count = old_table->bs_column_count; + int32_t i_new = 0, new_column_count = new_table->bs_column_count; + + if (!old_column_count || !new_column_count) { + return; + } + + ecs_bs_column_t *old_columns = old_data->bs_columns; + ecs_bs_column_t *new_columns = new_data->bs_columns; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t offset_new = new_table->bs_column_offset; + int32_t offset_old = old_table->bs_column_offset; + + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new + offset_new]; + ecs_entity_t old_component = old_components[i_old + offset_old]; + + if (new_component == old_component) { + ecs_bitset_t *old_bs = &old_columns[i_old].data; + ecs_bitset_t *new_bs = &new_columns[i_new].data; + + ecs_bitset_ensure(new_bs, new_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = ecs_bitset_get(old_bs, old_index + i); + ecs_bitset_set(new_bs, new_index + i, value); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +static +void ensure_data( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t * column_count_out, + int32_t * sw_column_count_out, + int32_t * bs_column_count_out, + ecs_column_t ** columns_out, + ecs_sw_column_t ** sw_columns_out, + ecs_bs_column_t ** bs_columns_out) +{ + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + + /* It is possible that the table data was created without content. + * Now that data is going to be written to the table, initialize */ + if (column_count | sw_column_count | bs_column_count) { + columns = data->columns; + sw_columns = data->sw_columns; + bs_columns = data->bs_columns; + + if (!columns && !sw_columns && !bs_columns) { + ecs_init_data(world, table, data); + columns = data->columns; + sw_columns = data->sw_columns; + bs_columns = data->bs_columns; + + ecs_assert(sw_column_count == 0 || sw_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(bs_column_count == 0 || bs_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + } + + *column_count_out = column_count; + *sw_column_count_out = sw_column_count; + *bs_column_count_out = bs_column_count; + *columns_out = columns; + *sw_columns_out = sw_columns; + *bs_columns_out = bs_columns; + } +} + +static +void grow_column( + ecs_world_t * world, + ecs_entity_t * entities, + ecs_column_t * column, + ecs_c_info_t * c_info, + int32_t to_add, + int32_t new_size, + bool construct) +{ + ecs_vector_t *vec = column->data; + int16_t alignment = column->alignment; + + int32_t size = column->size; + int32_t count = ecs_vector_count(vec); + int32_t old_size = ecs_vector_size(vec); + int32_t new_count = count + to_add; + bool can_realloc = new_size != old_size; + + ecs_assert(new_size >= new_count, ECS_INTERNAL_ERROR, NULL); + + /* If the array could possibly realloc and the component has a move action + * defined, move old elements manually */ + ecs_move_t move; + if (c_info && count && can_realloc && (move = c_info->lifecycle.move)) { + ecs_xtor_t ctor = c_info->lifecycle.ctor; + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Create new vector */ + ecs_vector_t *new_vec = ecs_vector_new_t(size, alignment, new_size); + ecs_vector_set_count_t(&new_vec, size, alignment, new_count); + + void *old_buffer = ecs_vector_first_t( + vec, size, alignment); + + void *new_buffer = ecs_vector_first_t( + new_vec, size, alignment); + + /* First construct elements (old and new) in new buffer */ + ctor(world, c_info->component, entities, new_buffer, + ecs_to_size_t(size), construct ? new_count : count, + c_info->lifecycle.ctx); + + /* Move old elements */ + move(world, c_info->component, entities, entities, + new_buffer, old_buffer, ecs_to_size_t(size), count, + c_info->lifecycle.ctx); + + /* Free old vector */ + ecs_vector_free(vec); + column->data = new_vec; + } else { + /* If array won't realloc or has no move, simply add new elements */ + if (can_realloc) { + ecs_vector_set_size_t(&vec, size, alignment, new_size); + } + + void *elem = ecs_vector_addn_t(&vec, size, alignment, to_add); + + ecs_xtor_t ctor; + if (construct && c_info && (ctor = c_info->lifecycle.ctor)) { + /* If new elements need to be constructed and component has a + * constructor, construct */ + ctor(world, c_info->component, &entities[count], elem, + ecs_to_size_t(size), to_add, c_info->lifecycle.ctx); + } + + column->data = vec; + } + + ecs_assert(ecs_vector_size(column->data) == new_size, + ECS_INTERNAL_ERROR, NULL); +} + +static +int32_t grow_data( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t cur_count = ecs_table_data_count(data); + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + + /* Add record to record ptr array */ + ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size); + ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_vector_size(data->record_ptrs) > size) { + size = ecs_vector_size(data->record_ptrs); + } + + /* Add entity to column with entity ids */ + ecs_vector_set_size(&data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL); + + /* Initialize entity ids and record ptrs */ + int32_t i; + if (ids) { + for (i = 0; i < to_add; i ++) { + e[i] = ids[i]; + } + } else { + ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); + } + ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); + + /* Add elements to each column array */ + ecs_c_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + if (!column->size) { + continue; + } + + ecs_c_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; + } + + grow_column(world, entities, column, c_info, to_add, size, true); + ecs_assert(ecs_vector_size(columns[i].data) == size, + ECS_INTERNAL_ERROR, NULL); + } + + /* Add elements to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_switch_t *sw = sw_columns[i].data; + ecs_switch_addn(sw, to_add); + } + + /* Add elements to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i].data; + ecs_bitset_addn(bs, to_add); + } + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + if (!world->in_progress && !cur_count) { + ecs_table_activate(world, table, 0, true); + } + + table->alloc_count ++; + + /* Return index of first added entity */ + return cur_count; +} + +static +void fast_append( + ecs_column_t *columns, + int32_t column_count) +{ + /* Add elements to each column array */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + int16_t size = column->size; + if (size) { + int16_t alignment = column->alignment; + ecs_vector_add_t(&column->data, size, alignment); + } + } +} + +int32_t ecs_table_append( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + ecs_entity_t entity, + ecs_record_t * record, + bool construct) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + int32_t count = ecs_vector_count(data->entities); + int32_t size = ecs_vector_size(data->entities); + + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + + /* Grow buffer with entity ids, set new element to new entity */ + ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + *e = entity; + + /* Keep track of alloc count. This allows references to check if cached + * pointers need to be updated. */ + table->alloc_count += (count == size); + + /* Add record ptr to array with record ptrs */ + ecs_record_t **r = ecs_vector_add(&data->record_ptrs, ecs_record_t*); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + *r = record; + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + /* If this is the first entity in this table, signal queries so that the + * table moves from an inactive table to an active table. */ + if (!world->in_progress && !count) { + ecs_table_activate(world, table, 0, true); + } + + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & EcsTableIsComplex)) { + fast_append(columns, column_count); + return count; + } + + ecs_c_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + /* Reobtain size to ensure that the columns have the same size as the + * entities and record vectors. This keeps reasoning about when allocations + * occur easier. */ + size = ecs_vector_size(data->entities); + + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + if (!column->size) { + continue; + } + + ecs_c_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; + } + + grow_column(world, entities, column, c_info, 1, size, construct); + + ecs_assert( + ecs_vector_size(columns[i].data) == ecs_vector_size(data->entities), + ECS_INTERNAL_ERROR, NULL); + + ecs_assert( + ecs_vector_count(columns[i].data) == ecs_vector_count(data->entities), + ECS_INTERNAL_ERROR, NULL); + } + + /* Add element to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = sw_columns[i].data; + ecs_switch_add(sw); + columns[i + table->sw_column_offset].data = ecs_switch_values(sw); + } + + /* Add element to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i].data; + ecs_bitset_addn(bs, 1); + } + + return count; +} + +static +void fast_delete_last( + ecs_column_t *columns, + int32_t column_count) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vector_remove_last(column->data); + } +} + +static +void fast_delete( + ecs_column_t *columns, + int32_t column_count, + int32_t index) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + int16_t size = column->size; + if (size) { + int16_t alignment = column->alignment; + ecs_vector_remove_index_t(column->data, size, alignment, index); + } + } +} + +void ecs_table_delete( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t index, + bool destruct) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *entity_column = data->entities; + int32_t count = ecs_vector_count(entity_column); + + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + + ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); + + ecs_c_info_t **c_info_array = table->c_info; + int32_t column_count = table->column_count; + int32_t i; + + ecs_entity_t *entities = ecs_vector_first(entity_column, ecs_entity_t); + ecs_entity_t entity_to_move = entities[count]; + + /* Move last entity id to index */ + entities[index] = entity_to_move; + ecs_vector_remove_last(entity_column); + + /* Move last record ptr to index */ + ecs_vector_t *record_column = data->record_ptrs; + ecs_record_t **records = ecs_vector_first(record_column, ecs_record_t*); + + ecs_assert(count < ecs_vector_count(record_column), ECS_INTERNAL_ERROR, NULL); + ecs_record_t *record_to_move = records[count]; + + records[index] = record_to_move; + ecs_vector_remove_last(record_column); + + /* Update record of moved entity in entity index */ + if (index != count) { + if (record_to_move) { + if (record_to_move->row >= 0) { + record_to_move->row = index + 1; + } else { + record_to_move->row = -(index + 1); + } + ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); + } + } + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + if (!count) { + ecs_table_activate(world, table, NULL, false); + } + + /* Move each component value in array to index */ + ecs_column_t *columns = data->columns; + + if (!(table->flags & EcsTableIsComplex)) { + if (index == count) { + fast_delete_last(columns, column_count); + } else { + fast_delete(columns, column_count, index); + } + return; + } + + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + int16_t size = column->size; + int16_t alignment = column->alignment; + if (size) { + ecs_c_info_t *c_info = c_info_array ? c_info_array[i] : NULL; + ecs_xtor_t dtor; + + void *dst = ecs_vector_get_t(column->data, size, alignment, index); + + ecs_move_t move; + if (c_info && (count != index) && (move = c_info->lifecycle.move)) { + void *ctx = c_info->lifecycle.ctx; + void *src = ecs_vector_get_t(column->data, size, alignment, count); + ecs_entity_t component = c_info->component; + + /* If the delete is not destructing the component, the component + * was already deleted, most likely by a move. In that case we + * still need to move, but we need to make sure we're moving + * into an element that is initialized with valid memory, so + * call the constructor. */ + if (!destruct) { + ecs_xtor_t ctor = c_info->lifecycle.ctor; + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ctor(world, c_info->component, &entity_to_move, dst, + ecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } + + /* Move last element into deleted element */ + move(world, component, &entity_to_move, &entity_to_move, dst, src, + ecs_to_size_t(size), 1, ctx); + + /* Memory has been copied, we can now simply remove last */ + ecs_vector_remove_last(column->data); + } else { + if (destruct && c_info && (dtor = c_info->lifecycle.dtor)) { + dtor(world, c_info->component, &entities[index], dst, + ecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } + + ecs_vector_remove_index_t(column->data, size, alignment, index); + } + } + } + + /* Remove elements from switch columns */ + ecs_sw_column_t *sw_columns = data->sw_columns; + int32_t sw_column_count = table->sw_column_count; + for (i = 0; i < sw_column_count; i ++) { + ecs_switch_remove(sw_columns[i].data, index); + } + + /* Remove elements from bitset columns */ + ecs_bs_column_t *bs_columns = data->bs_columns; + int32_t bs_column_count = table->bs_column_count; + for (i = 0; i < bs_column_count; i ++) { + ecs_bitset_remove(&bs_columns[i].data, index); + } +} + +static +void fast_move( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index) +{ + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t i_new = 0, new_column_count = new_table->column_count; + int32_t i_old = 0, old_column_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + + if (size) { + int16_t alignment = new_column->alignment; + void *dst = ecs_vector_get_t(new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t(old_column->data, size, alignment, old_index); + + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(dst, src, size); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +void ecs_table_move( + ecs_world_t * world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index) +{ + ecs_assert(new_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(old_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(old_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!((new_table->flags | old_table->flags) & EcsTableIsComplex)) { + fast_move(new_table, new_data, new_index, old_table, old_data, old_index); + return; + } + + move_switch_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); + + move_bitset_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); + + bool same_entity = dst_entity == src_entity; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t i_new = 0, new_column_count = new_table->column_count; + int32_t i_old = 0, old_column_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + int16_t alignment = new_column->alignment; + + if (size) { + void *dst = ecs_vector_get_t(new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t(old_column->data, size, alignment, old_index); + + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_c_info_t *cdata = new_table->c_info[i_new]; + if (same_entity) { + ecs_move_t move; + if (cdata && (move = cdata->lifecycle.move)) { + void *ctx = cdata->lifecycle.ctx; + ecs_xtor_t ctor = cdata->lifecycle.ctor; + + /* Ctor should always be set if copy is set */ + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Construct a new value, move the value to it */ + ctor(world, new_component, &dst_entity, dst, + ecs_to_size_t(size), 1, ctx); + + move(world, new_component, &dst_entity, &src_entity, + dst, src, ecs_to_size_t(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + } else { + ecs_copy_t copy; + if (cdata && (copy = cdata->lifecycle.copy)) { + void *ctx = cdata->lifecycle.ctx; + ecs_xtor_t ctor = cdata->lifecycle.ctor; + + /* Ctor should always be set if copy is set */ + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ctor(world, new_component, &dst_entity, dst, + ecs_to_size_t(size), 1, ctx); + copy(world, new_component, &dst_entity, &src_entity, + dst, src, ecs_to_size_t(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + } + } + } else { + if (new_component < old_component) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); + } else { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } + + for (; (i_new < new_column_count); i_new ++) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); + } + + for (; (i_old < old_column_count); i_old ++) { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); + } +} + +int32_t ecs_table_appendn( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t to_add, + const ecs_entity_t *ids) +{ + int32_t cur_count = ecs_table_data_count(data); + return grow_data(world, table, data, to_add, cur_count + to_add, ids); +} + +void ecs_table_set_size( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t size) +{ + int32_t cur_count = ecs_table_data_count(data); + + if (cur_count < size) { + grow_data(world, table, data, 0, size, NULL); + } else if (!size) { + /* Initialize columns if 0 is passed. This is a shortcut to initialize + * columns when, for example, an API call is inserting bulk data. */ + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns; + ecs_sw_column_t *sw_columns; + ecs_bs_column_t *bs_columns; + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + } +} + +int32_t ecs_table_data_count( + ecs_data_t *data) +{ + return data ? ecs_vector_count(data->entities) : 0; +} + +static +void swap_switch_columns( + ecs_table_t * table, + ecs_data_t * data, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->sw_column_count; + if (!column_count) { + return; + } + + ecs_sw_column_t *columns = data->sw_columns; + + for (i = 0; i < column_count; i ++) { + ecs_switch_t *sw = columns[i].data; + ecs_switch_swap(sw, row_1, row_2); + } +} + +static +void swap_bitset_columns( + ecs_table_t * table, + ecs_data_t * data, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->bs_column_count; + if (!column_count) { + return; + } + + ecs_bs_column_t *columns = data->bs_columns; + + for (i = 0; i < column_count; i ++) { + ecs_bitset_t *bs = &columns[i].data; + ecs_bitset_swap(bs, row_1, row_2); + } +} + +void ecs_table_swap( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row_1, + int32_t row_2) +{ + (void)world; + + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_column_t *columns = data->columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); + + if (row_1 == row_2) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; + + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + ecs_record_t *record_ptr_1 = record_ptrs[row_1]; + ecs_record_t *record_ptr_2 = record_ptrs[row_2]; + + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Swap entities */ + entities[row_1] = e2; + entities[row_2] = e1; + record_ptr_1->row = row_2; + record_ptr_2->row = row_1; + record_ptrs[row_1] = record_ptr_2; + record_ptrs[row_2] = record_ptr_1; + + if (row_2 < 0) { + record_ptr_1->row --; + } else { + record_ptr_1->row ++; + } + if (row_1 < 0) { + record_ptr_2->row --; + } else { + record_ptr_2->row ++; + } + + /* Swap columns */ + int32_t i, column_count = table->column_count; + + for (i = 0; i < column_count; i ++) { + int16_t size = columns[i].size; + int16_t alignment = columns[i].alignment; + void *ptr = ecs_vector_first_t(columns[i].data, size, alignment); + + if (size) { + void *tmp = ecs_os_alloca(size); + + void *el_1 = ECS_OFFSET(ptr, size * row_1); + void *el_2 = ECS_OFFSET(ptr, size * row_2); + + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } + } + + swap_switch_columns(table, data, row_1, row_2); + swap_bitset_columns(table, data, row_1, row_2); + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); +} + +static +void merge_vector( + ecs_vector_t ** dst_out, + ecs_vector_t * src, + int16_t size, + int16_t alignment) +{ + ecs_vector_t *dst = *dst_out; + int32_t dst_count = ecs_vector_count(dst); + + if (!dst_count) { + if (dst) { + ecs_vector_free(dst); + } + + *dst_out = src; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = ecs_vector_count(src); + ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); + + void *dst_ptr = ecs_vector_first_t(dst, size, alignment); + void *src_ptr = ecs_vector_first_t(src, size, alignment); + + dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + + ecs_vector_free(src); + *dst_out = dst; + } +} + +static +void merge_table_data( + ecs_world_t * world, + ecs_table_t * new_table, + ecs_table_t * old_table, + int32_t old_count, + int32_t new_count, + ecs_data_t * old_data, + ecs_data_t * new_data) +{ + int32_t i_new, new_component_count = new_table->column_count; + int32_t i_old = 0, old_component_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_table->type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_table->type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + if (!new_columns && !new_data->entities) { + ecs_init_data(world, new_table, new_data); + new_columns = new_data->columns; + } + + if (!old_count) { + return; + } + + /* Merge entities */ + merge_vector(&new_data->entities, old_data->entities, ECS_SIZEOF(ecs_entity_t), + ECS_ALIGNOF(ecs_entity_t)); + old_data->entities = NULL; + ecs_entity_t *entities = ecs_vector_first(new_data->entities, ecs_entity_t); + + ecs_assert(ecs_vector_count(new_data->entities) == old_count + new_count, + ECS_INTERNAL_ERROR, NULL); + + /* Merge entity index record pointers */ + merge_vector(&new_data->record_ptrs, old_data->record_ptrs, + ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*)); + old_data->record_ptrs = NULL; + + for (i_new = 0; (i_new < new_component_count) && (i_old < old_component_count); ) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + int16_t size = new_columns[i_new].size; + int16_t alignment = new_columns[i_new].alignment; + + if ((new_component & ECS_ROLE_MASK) || + (old_component & ECS_ROLE_MASK)) + { + break; + } + + if (new_component == old_component) { + merge_vector( + &new_columns[i_new].data, old_columns[i_old].data, size, + alignment); + + old_columns[i_old].data = NULL; + + /* Mark component column as dirty */ + mark_table_dirty(new_table, i_new + 1); + + i_new ++; + i_old ++; + } else if (new_component < old_component) { + /* New column does not occur in old table, make sure vector is large + * enough. */ + if (size) { + ecs_column_t *column = &new_columns[i_new]; + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + + /* Construct new values */ + ecs_c_info_t *c_info; + ecs_xtor_t ctor; + if ((c_info = new_table->c_info[i_new]) && + (ctor = c_info->lifecycle.ctor)) + { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); + } + } + + i_new ++; + } else if (new_component > old_component) { + if (size) { + ecs_column_t *column = &old_columns[i_old]; + + /* Destruct old values */ + ecs_c_info_t *c_info; + ecs_xtor_t dtor; + if ((c_info = old_table->c_info[i_old]) && + (dtor = c_info->lifecycle.dtor)) + { + dtor_component(world, c_info, column, + entities, 0, old_count); + } + + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; + + i_old ++; + } + } + } + + move_switch_columns( + new_table, new_data, new_count, old_table, old_data, 0, old_count); + + /* Initialize remaining columns */ + for (; i_new < new_component_count; i_new ++) { + ecs_column_t *column = &new_columns[i_new]; + int16_t size = column->size; + int16_t alignment = column->alignment; + + if (size) { + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + + /* Construct new values */ + ecs_c_info_t *c_info; + ecs_xtor_t ctor; + if ((c_info = new_table->c_info[i_new]) && + (ctor = c_info->lifecycle.ctor)) + { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); + } + } + } + + /* Destroy remaining columns */ + for (; i_old < old_component_count; i_old ++) { + ecs_column_t *column = &old_columns[i_old]; + + /* Destruct old values */ + ecs_c_info_t *c_info; + ecs_xtor_t dtor; + if ((c_info = old_table->c_info[i_old]) && + (dtor = c_info->lifecycle.dtor)) + { + dtor_component(world, c_info, column, entities, + 0, old_count); + } + + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; + } + + /* Mark entity column as dirty */ + mark_table_dirty(new_table, 0); +} + +int32_t ecs_table_count( + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_data_t *data = table->data; + if (!data) { + return 0; + } + + return ecs_table_data_count(data); +} + +ecs_data_t* ecs_table_merge( + ecs_world_t * world, + ecs_table_t *new_table, + ecs_table_t *old_table, + ecs_data_t *new_data, + ecs_data_t *old_data) +{ + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + bool move_data = false; + + /* If there is nothing to merge to, just clear the old table */ + if (!new_table) { + ecs_table_clear_data(old_table, old_data); + return NULL; + } + + /* If there is no data to merge, drop out */ + if (!old_data) { + return NULL; + } + + if (!new_data) { + new_data = ecs_table_get_or_create_data(new_table); + if (new_table == old_table) { + move_data = true; + } + } + + ecs_entity_t *old_entities = ecs_vector_first(old_data->entities, ecs_entity_t); + + int32_t old_count = ecs_vector_count(old_data->entities); + int32_t new_count = ecs_vector_count(new_data->entities); + + ecs_record_t **old_records = ecs_vector_first( + old_data->record_ptrs, ecs_record_t*); + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < old_count; i ++) { + ecs_record_t *record; + if (new_table != old_table) { + record = old_records[i]; + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + record = ecs_eis_get_or_create(world, old_entities[i]); + } + + bool is_monitored = record->row < 0; + record->row = ecs_row_to_record(new_count + i, is_monitored); + record->table = new_table; + } + + /* Merge table columns */ + if (move_data) { + *new_data = *old_data; + } else { + merge_table_data(world, new_table, old_table, old_count, new_count, + old_data, new_data); + } + + new_table->alloc_count ++; + + if (!new_count && old_count) { + ecs_table_activate(world, new_table, NULL, true); + } + + return new_data; +} + +void ecs_table_replace_data( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data) +{ + int32_t prev_count = 0; + ecs_data_t *table_data = table->data; + ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); + + if (table_data) { + prev_count = ecs_vector_count(table_data->entities); + run_remove_actions( + world, table, table_data, 0, ecs_table_data_count(table_data), false); + ecs_table_clear_data(table, table_data); + } + + if (data) { + table_data = ecs_table_get_or_create_data(table); + *table_data = *data; + } else { + return; + } + + int32_t count = ecs_table_count(table); + + if (!prev_count && count) { + ecs_table_activate(world, table, 0, true); + } else if (prev_count && !count) { + ecs_table_activate(world, table, 0, false); + } +} + +bool ecs_table_match_filter( + ecs_world_t * world, + ecs_table_t * table, + const ecs_filter_t * filter) +{ + if (!filter) { + return true; + } + + ecs_type_t type = table->type; + + if (filter->include) { + /* If filter kind is exact, types must be the same */ + if (filter->include_kind == EcsMatchExact) { + if (type != filter->include) { + return false; + } + + /* Default for include_kind is MatchAll */ + } else if (!ecs_type_contains(world, type, filter->include, + filter->include_kind != EcsMatchAny, true)) + { + return false; + } + } + + if (filter->exclude) { + /* If filter kind is exact, types must be the same */ + if (filter->exclude_kind == EcsMatchExact) { + if (type == filter->exclude) { + return false; + } + + /* Default for exclude_kind is MatchAny */ + } else if (ecs_type_contains(world, type, filter->exclude, + filter->exclude_kind == EcsMatchAll, true)) + { + return false; + } + } + + return true; +} + +int32_t* ecs_table_get_dirty_state( + ecs_table_t *table) +{ + if (!table->dirty_state) { + table->dirty_state = ecs_os_calloc(ECS_SIZEOF(int32_t) * (table->column_count + 1)); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + } + return table->dirty_state; +} + +int32_t* ecs_table_get_monitor( + ecs_table_t *table) +{ + int32_t *dirty_state = ecs_table_get_dirty_state(table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t column_count = table->column_count; + return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); +} + +void ecs_table_notify( + ecs_world_t * world, + ecs_table_t * table, + ecs_table_event_t * event) +{ + switch(event->kind) { + case EcsTableQueryMatch: + register_query( + world, table, event->query, event->matched_table_index); + break; + case EcsTableQueryUnmatch: + unregister_query( + world, table, event->query); + break; + case EcsTableComponentInfo: + notify_component_info(world, table, event->component); + break; + } +} + + +static +const ecs_entity_t* new_w_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_entities_t *component_ids, + int32_t count, + void **c_info, + int32_t *row_out); + +static +void* get_component_w_index( + ecs_entity_info_t *info, + int32_t index) +{ + ecs_data_t *data = info->data; + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_column_t *columns = data->columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index < info->table->column_count, ECS_INVALID_COMPONENT_ID, NULL); + + ecs_column_t *column = &columns[index]; + ecs_vector_t *data_vec = column->data; + int16_t size = column->size; + + /* If size is 0, component does not have a value. This is likely caused by + * an application trying to call ecs_get with a tag. */ + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + + /* This function should not be called if an entity does not exist in the + * provided table. Therefore if the component is found in the table, and an + * entity exists for it, the vector cannot be NULL */ + ecs_assert(data_vec != NULL, ECS_INTERNAL_ERROR, NULL); + + void *ptr = ecs_vector_first_t(data_vec, size, column->alignment); + + /* This could only happen when the vector is empty, which should not be + * possible since the vector should at least have one element */ + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + return ECS_OFFSET(ptr, info->row * size); +} + +/* Get pointer to single component value */ +static +void* get_component( + ecs_entity_info_t *info, + ecs_entity_t component) +{ + ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(info->row >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = info->table; + ecs_type_t type = table->type; + + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + + /* The table column_count contains the maximum column index that actually + * contains data. This excludes component ids that do not have data, such + * as tags. Therefore it is faster to iterate column_count vs. all the + * elements in the type. + * + * The downside of this is that the code can't always detect when an + * application attempts to get the value of a tag (which is not allowed). To + * ensure consistent behavior in debug mode, the entire type is iterated as + * this guarantees that the code will assert when attempting to obtain the + * value of a tag. */ +#ifndef NDEBUG + int i, count = ecs_vector_count(type); +#else + int i, count = table->column_count; +#endif + + for (i = 0; i < count; i ++) { + if (ids[i] == component) { + return get_component_w_index(info, i); + } + } + + return NULL; +} + +/* Utility to compute actual row from row in record */ +static +int32_t set_row_info( + ecs_entity_info_t *info, + int32_t row) +{ + return info->row = ecs_record_to_row(row, &info->is_watched); +} + +/* Utility to set info from main stage record */ +static +void set_info_from_record( + ecs_entity_t e, + ecs_entity_info_t * info, + ecs_record_t * record) +{ + (void)e; + + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + + info->record = record; + + ecs_table_t *table = record->table; + + set_row_info(info, record->row); + + info->table = table; + if (!info->table) { + return; + } + + ecs_data_t *data = ecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + info->data = data; + + ecs_assert(ecs_vector_count(data->entities) > info->row, + ECS_INTERNAL_ERROR, NULL); +} + +/* Get info from main stage */ +bool ecs_get_info( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info) +{ + info->table = NULL; + + if (entity & ECS_ROLE) { + return false; + } + + ecs_record_t *record = ecs_eis_get(world, entity); + + if (!record) { + info->is_watched = false; + info->record = NULL; + return false; + } + + set_info_from_record(entity, info, record); + + return true; +} + +static +ecs_c_info_t *get_c_info( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_entity_t real_id = ecs_get_typeid(world, component); + if (real_id) { + return ecs_get_c_info(world, real_id); + } else { + return NULL; + } +} + +void ecs_get_column_info( + ecs_world_t * world, + ecs_table_t * table, + ecs_entities_t * components, + ecs_column_info_t * cinfo, + bool get_all) +{ + int32_t column_count = table->column_count; + ecs_entity_t *type_array = ecs_vector_first(table->type, ecs_entity_t); + + if (get_all) { + int32_t i, count = ecs_vector_count(table->type); + for (i = 0; i < count; i ++) { + ecs_entity_t id = type_array[i]; + cinfo[i].id = id; + cinfo[i].ci = get_c_info(world, id); + cinfo[i].column = i; + } + } else { + ecs_entity_t *array = components->array; + int32_t i, cur, count = components->count; + for (i = 0; i < count; i ++) { + ecs_entity_t id = array[i]; + cinfo[i].id = id; + cinfo[i].ci = get_c_info(world, id); + cinfo[i].column = -1; + + for (cur = 0; cur < column_count; cur ++) { + if (type_array[cur] == id) { + cinfo[i].column = cur; + break; + } + } + } + } +} + +static +void run_component_trigger_for_entities( + ecs_world_t * world, + ecs_vector_t * trigger_vec, + ecs_entity_t component, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_entity_t *entities) +{ + (void)world; + int32_t i, trigger_count = ecs_vector_count(trigger_vec); + if (trigger_count) { + EcsTrigger *triggers = ecs_vector_first(trigger_vec, EcsTrigger); + int32_t index = ecs_type_index_of(table->type, component); + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + index ++; + + ecs_entity_t components[1] = { component }; + ecs_type_t types[1] = { ecs_type_from_entity(world, component) }; + int32_t columns[1] = { index }; + + /* If this is a tag, don't try to retrieve data */ + if (table->column_count < index) { + columns[0] = 0; + } else { + ecs_column_t *column = &data->columns[index - 1]; + if (!column->size) { + columns[0] = 0; + } + } + + ecs_iter_table_t table_data = { + .table = table, + .columns = columns, + .components = components, + .types = types + }; + + ecs_iter_t it = { + .world = world, + .table = &table_data, + .table_count = 1, + .inactive_table_count = 1, + .column_count = 1, + .table_columns = data->columns, + .entities = entities, + .offset = row, + .count = count, + }; + + for (i = 0; i < trigger_count; i ++) { + it.system = triggers[i].self; + it.param = triggers[i].ctx; + triggers[i].action(&it); + } + } +} + +static +void ecs_run_component_trigger( + ecs_world_t * world, + ecs_vector_t * trigger_vec, + ecs_entity_t component, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count) +{ + ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL); + + if (table->flags & EcsTableIsPrefab) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + + entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row); + + run_component_trigger_for_entities( + world, trigger_vec, component, table, data, row, count, entities); +} + +#ifdef FLECS_SYSTEM +static +void run_set_systems_for_entities( + ecs_world_t * world, + ecs_entities_t * components, + ecs_table_t * table, + int32_t row, + int32_t count, + ecs_entity_t * entities, + bool set_all) +{ + if (set_all) { + /* Run OnSet systems for all components of the entity. This usually + * happens when an entity is created directly in its target table. */ + ecs_vector_t *queries = table->on_set_all; + ecs_vector_each(queries, ecs_matched_query_t, m, { + ecs_run_monitor(world, m, components, row, count, entities); + }); + } else { + /* Run OnSet systems for a specific component. This usually happens when + * an application calls ecs_set or ecs_modified. The entity's table + * stores a vector for each component with the OnSet systems for that + * component. This vector maintains the same order as the table's type, + * which makes finding the correct set of systems as simple as getting + * the index of a component id in the table type. + * + * One thing to note is that the system may be invoked for a table that + * is not the same as the entity for which the system is invoked. This + * can happen in the case of instancing, where adding an INSTANCEOF + * relationship conceptually adds components to an entity, but the + * actual components are stored on the base entity. */ + ecs_vector_t **on_set_systems = table->on_set; + if (on_set_systems) { + int32_t index = ecs_type_index_of(table->type, components->array[0]); + + /* This should never happen, as an OnSet system should only ever be + * invoked for entities that have the component for which this + * function was invoked. */ + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *queries = on_set_systems[index]; + ecs_vector_each(queries, ecs_matched_query_t, m, { + ecs_run_monitor(world, m, components, row, count, entities); + }); + } + } +} +#endif + +void ecs_run_set_systems( + ecs_world_t * world, + ecs_entities_t * components, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + bool set_all) +{ +#ifdef FLECS_SYSTEM + if (!count || !data) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + + entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row); + + run_set_systems_for_entities(world, components, table, row, + count, entities, set_all); +#endif +} + +void ecs_run_monitors( + ecs_world_t * world, + ecs_table_t * dst_table, + ecs_vector_t * v_dst_monitors, + int32_t dst_row, + int32_t count, + ecs_vector_t *v_src_monitors) +{ +#ifdef FLECS_SYSTEM + if (v_dst_monitors == v_src_monitors) { + return; + } + + if (!v_dst_monitors) { + return; + } + + ecs_assert(!(dst_table->flags & EcsTableIsPrefab), ECS_INTERNAL_ERROR, NULL); + (void)dst_table; + + if (!v_src_monitors) { + ecs_vector_each(v_dst_monitors, ecs_matched_query_t, monitor, { + ecs_run_monitor(world, monitor, NULL, dst_row, count, NULL); + }); + } else { + /* If both tables have monitors, run the ones that dst_table has and + * src_table doesn't have */ + int32_t i, m_count = ecs_vector_count(v_dst_monitors); + int32_t j = 0, src_count = ecs_vector_count(v_src_monitors); + ecs_matched_query_t *dst_monitors = ecs_vector_first(v_dst_monitors, ecs_matched_query_t); + ecs_matched_query_t *src_monitors = ecs_vector_first(v_src_monitors, ecs_matched_query_t); + + for (i = 0; i < m_count; i ++) { + ecs_matched_query_t *dst = &dst_monitors[i]; + + ecs_entity_t system = dst->query->system; + ecs_assert(system != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_matched_query_t *src = 0; + while (j < src_count) { + src = &src_monitors[j]; + if (src->query->system < system) { + j ++; + } else { + break; + } + } + + if (src->query->system == system) { + continue; + } + + ecs_run_monitor(world, dst, NULL, dst_row, count, NULL); + } + } +#endif +} + +static +int32_t find_prefab( + ecs_type_t type, + int32_t n) +{ + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *buffer = ecs_vector_first(type, ecs_entity_t); + + for (i = n + 1; i < count; i ++) { + ecs_entity_t e = buffer[i]; + if (ECS_HAS_ROLE(e, INSTANCEOF)) { + return i; + } + } + + return -1; +} + +static +void instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count); + +static +void instantiate_children( + ecs_world_t * world, + ecs_entity_t base, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_table_t * child_table) +{ + ecs_type_t type = child_table->type; + ecs_data_t *child_data = ecs_table_get_data(child_table); + if (!child_data || !ecs_table_data_count(child_data)) { + return; + } + + int32_t column_count = child_table->column_count; + ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); + int32_t type_count = ecs_vector_count(type); + + /* Instantiate child table for each instance */ + + /* Create component array for creating the table */ + ecs_entities_t components = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * type_count + 1) + }; + + void **c_info = ecs_os_alloca(ECS_SIZEOF(void*) * column_count); + + /* Copy in component identifiers. Find the base index in the component + * array, since we'll need this to replace the base with the instance id */ + int i, base_index = -1, pos = 0; + + for (i = 0; i < type_count; i ++) { + ecs_entity_t c = type_array[i]; + + /* Make sure instances don't have EcsPrefab */ + if (c == EcsPrefab) { + continue; + } + + /* Keep track of the element that creates the CHILDOF relationship with + * the prefab parent. We need to replace this element to make sure the + * created children point to the instance and not the prefab */ + if (ECS_HAS_ROLE(c, CHILDOF) && (c & ECS_COMPONENT_MASK) == base) { + base_index = pos; + } + + /* Store pointer to component array. We'll use this component array to + * create our new entities in bulk with new_w_data */ + if (i < column_count) { + ecs_column_t *column = &child_data->columns[i]; + c_info[pos] = ecs_vector_first_t( + column->data, column->size, column->alignment); + } + + components.array[pos] = c; + pos ++; + } + + ecs_assert(base_index != -1, ECS_INTERNAL_ERROR, NULL); + + /* If children are added to a prefab, make sure they are prefabs too */ + if (table->flags & EcsTableIsPrefab) { + components.array[pos] = EcsPrefab; + pos ++; + } + + components.count = pos; + + /* Instantiate the prefab child table for each new instance */ + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t child_count = ecs_vector_count(child_data->entities); + + for (i = row; i < count + row; i ++) { + ecs_entity_t instance = entities[i]; + + /* Replace CHILDOF element in the component array with instance id */ + components.array[base_index] = ECS_CHILDOF | instance; + + /* Find or create table */ + ecs_table_t *i_table = ecs_table_find_or_create(world, &components); + ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); + + /* The instance is trying to instantiate from a base that is also + * its parent. This would cause the hierarchy to instantiate itself + * which would cause infinite recursion. */ + int j; + ecs_entity_t *children = ecs_vector_first( + child_data->entities, ecs_entity_t); +#ifndef NDEBUG + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_assert(child != instance, ECS_INVALID_PARAMETER, NULL); + } +#endif + + /* Create children */ + int32_t child_row; + new_w_data(world, i_table, NULL, child_count, c_info, &child_row); + + /* If prefab child table has children itself, recursively instantiate */ + ecs_data_t *i_data = ecs_table_get_data(i_table); + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + instantiate(world, child, i_table, i_data, child_row + j, 1); + } + } +} + +static +void instantiate( + ecs_world_t * world, + ecs_entity_t base, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count) +{ + /* If base is a parent, instantiate children of base for instances */ + + ecs_vector_t *child_tables = ecs_map_get_ptr( + world->child_tables, ecs_vector_t*, base); + + if (child_tables) { + ecs_vector_each(child_tables, ecs_table_t*, child_table_ptr, { + instantiate_children( + world, base, table, data, row, count, *child_table_ptr); + }); + } +} + +static +bool override_component( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_t type, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count); + +static +bool override_from_base( + ecs_world_t * world, + ecs_entity_t base, + ecs_entity_t component, + ecs_data_t * data, + ecs_column_t * column, + int32_t row, + int32_t count) +{ + ecs_entity_info_t base_info; + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_get_info(world, base, &base_info) || !base_info.table) { + return false; + } + + void *base_ptr = get_component(&base_info, component); + if (base_ptr) { + int16_t data_size = column->size; + void *data_array = ecs_vector_first_t( + column->data, column->size, column->alignment); + void *data_ptr = ECS_OFFSET(data_array, data_size * row); + + component = ecs_get_typeid(world, component); + ecs_c_info_t *cdata = ecs_get_c_info(world, component); + int32_t index; + + ecs_copy_t copy = cdata ? cdata->lifecycle.copy : NULL; + if (copy) { + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + void *ctx = cdata->lifecycle.ctx; + for (index = 0; index < count; index ++) { + copy(world, component, &entities[row], &base, + data_ptr, base_ptr, ecs_to_size_t(data_size), 1, ctx); + data_ptr = ECS_OFFSET(data_ptr, data_size); + } + } else { + for (index = 0; index < count; index ++) { + ecs_os_memcpy(data_ptr, base_ptr, data_size); + data_ptr = ECS_OFFSET(data_ptr, data_size); + } + } + + return true; + } else { + /* If component not found on base, check if base itself inherits */ + ecs_type_t base_type = base_info.table->type; + return override_component(world, component, base_type, data, column, + row, count); + } +} + +static +bool override_component( + ecs_world_t * world, + ecs_entity_t component, + ecs_type_t type, + ecs_data_t * data, + ecs_column_t * column, + int32_t row, + int32_t count) +{ + ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); + int32_t i, type_count = ecs_vector_count(type); + + /* Walk prefabs */ + i = type_count - 1; + do { + ecs_entity_t e = type_array[i]; + + if (e < ECS_TYPE_ROLE_START) { + break; + } + + if (ECS_HAS_ROLE(e, INSTANCEOF)) { + if (override_from_base(world, e & ECS_COMPONENT_MASK, component, + data, column, row, count)) + { + return true; + } + } + } while (--i >= 0); + + return false; +} + +static +void ecs_components_override( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_column_info_t * component_info, + int32_t component_count, + bool run_on_set) +{ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(component_count != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table_without_base = table; + ecs_column_t *columns = data->columns; + ecs_type_t type = table->type; + int32_t column_count = table->column_count; + + int i; + for (i = 0; i < component_count; i ++) { + ecs_entity_t component = component_info[i].id; + + if (component >= ECS_HI_COMPONENT_ID) { + if (ECS_HAS_ROLE(component, INSTANCEOF)) { + ecs_entity_t base = component & ECS_COMPONENT_MASK; + + /* Illegal to create an instance of 0 */ + ecs_assert(base != 0, ECS_INVALID_PARAMETER, NULL); + instantiate(world, base, table, data, row, count); + + /* If table has on_set systems, get table without the base + * entity that was just added. This is needed to determine the + * diff between the on_set systems of the current table and the + * table without the base, as these are the systems that need to + * be invoked */ + ecs_entities_t to_remove = { + .array = &component, + .count = 1 + }; + table_without_base = ecs_table_traverse_remove(world, + table_without_base, &to_remove, NULL); + } + } + + int32_t column_index = component_info[i].column; + if (column_index == -1 || column_index >= column_count) { + continue; + } + + ecs_column_t *column = &columns[column_index]; + if (override_component(world, component, type, data, column, + row, count)) + { + ecs_entities_t to_remove = { + .array = &component, + .count = 1 + }; + table_without_base = ecs_table_traverse_remove(world, + table_without_base, &to_remove, NULL); + } + } + + /* Run OnSet actions when a base entity is added to the entity for + * components not overridden by the entity. */ + if (run_on_set && table_without_base != table) { + ecs_run_monitors(world, table, table->on_set_all, row, count, + table_without_base->on_set_all); + } +} + +static +void set_switch( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_entities_t *entities, + bool reset) +{ + ecs_entity_t *array = entities->array; + int32_t i, comp_count = entities->count; + + for (i = 0; i < comp_count; i ++) { + ecs_entity_t e = array[i]; + + if (ECS_HAS_ROLE(e, CASE)) { + e = e & ECS_COMPONENT_MASK; + + ecs_entity_t sw_case = 0; + if (!reset) { + sw_case = e; + ecs_assert(sw_case != 0, ECS_INTERNAL_ERROR, NULL); + } + + int32_t sw_index = ecs_table_switch_from_case(world, table, e); + ecs_assert(sw_index != -1, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = data->sw_columns[sw_index].data; + ecs_assert(sw != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t r; + for (r = 0; r < count; r ++) { + ecs_switch_set(sw, row + r, sw_case); + } + } + } +} + +static +void ecs_components_switch( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_entities_t *added, + ecs_entities_t *removed) +{ + if (added) { + set_switch(world, table, data, row, count, added, false); + } + if (removed) { + set_switch(world, table, data, row, count, removed, true); + } +} + +static +void ecs_components_on_add( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_column_info_t * component_info, + int32_t component_count) +{ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + int i; + for (i = 0; i < component_count; i ++) { + ecs_c_info_t *c_info = component_info[i].ci; + ecs_vector_t *triggers; + if (!c_info || !(triggers = c_info->on_add)) { + continue; + } + + ecs_entity_t component = component_info[i].id; + ecs_run_component_trigger( + world, triggers, component, table, data, row, count); + } +} + +static +void ecs_components_on_remove( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_column_info_t * component_info, + int32_t component_count) +{ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + int i; + for (i = 0; i < component_count; i ++) { + ecs_c_info_t *c_info = component_info[i].ci; + ecs_vector_t *triggers; + if (!c_info || !(triggers = c_info->on_remove)) { + continue; + } + + ecs_entity_t component = component_info[i].id; + ecs_run_component_trigger( + world, triggers, component, table, data, row, count); + } +} + +void ecs_run_add_actions( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_entities_t * added, + bool get_all, + bool run_on_set) +{ + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(added->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + + ecs_column_info_t cinfo[ECS_MAX_ADD_REMOVE]; + ecs_get_column_info(world, table, added, cinfo, get_all); + int added_count = added->count; + + if (table->flags & EcsTableHasBase) { + ecs_components_override( + world, table, data, row, count, cinfo, + added_count, run_on_set); + } + + if (table->flags & EcsTableHasSwitch) { + ecs_components_switch(world, table, data, row, count, added, NULL); + } + + if (table->flags & EcsTableHasOnAdd) { + ecs_components_on_add(world, table, data, row, count, + cinfo, added_count); + } +} + +void ecs_run_remove_actions( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_entities_t * removed, + bool get_all) +{ + ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(removed->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + + ecs_column_info_t cinfo[ECS_MAX_ADD_REMOVE]; + ecs_get_column_info(world, table, removed, cinfo, get_all); + int removed_count = removed->count; + + if (table->flags & EcsTableHasOnRemove) { + ecs_components_on_remove(world, table, data, + row, count, cinfo, removed_count); + } +} + +static +int32_t new_entity( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * new_table, + ecs_entities_t * added) +{ + ecs_record_t *record = info->record; + ecs_data_t *new_data = ecs_table_get_or_create_data(new_table); + int32_t new_row; + + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!record) { + record = ecs_eis_get_or_create(world, entity); + } + + new_row = ecs_table_append( + world, new_table, new_data, entity, record, true); + + record->table = new_table; + record->row = ecs_row_to_record(new_row, info->is_watched); + + ecs_assert( + ecs_vector_count(new_data[0].entities) > new_row, + ECS_INTERNAL_ERROR, NULL); + + if (new_table->flags & EcsTableHasAddActions) { + ecs_run_add_actions( + world, new_table, new_data, new_row, 1, added, true, true); + + if (new_table->flags & EcsTableHasMonitors) { + ecs_run_monitors( + world, new_table, new_table->monitors, new_row, 1, NULL); + } + } + + info->data = new_data; + + return new_row; +} + +static +int32_t move_entity( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * src_table, + ecs_data_t * src_data, + int32_t src_row, + ecs_table_t * dst_table, + ecs_entities_t * added, + ecs_entities_t * removed) +{ + ecs_data_t *dst_data = ecs_table_get_or_create_data(dst_table); + ecs_assert(src_data != dst_data, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(src_data->entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *record = info->record; + ecs_assert(!record || record == ecs_eis_get(world, entity), + ECS_INTERNAL_ERROR, NULL); + + int32_t dst_row = ecs_table_append(world, dst_table, dst_data, entity, + record, false); + + record->table = dst_table; + record->row = ecs_row_to_record(dst_row, info->is_watched); + + ecs_assert(ecs_vector_count(src_data->entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + + /* Copy entity & components from src_table to dst_table */ + if (src_table->type) { + ecs_table_move(world, entity, entity, dst_table, dst_data, dst_row, + src_table, src_data, src_row); + + /* If components were removed, invoke remove actions before deleting */ + if (removed && (src_table->flags & EcsTableHasRemoveActions)) { + /* If entity was moved, invoke UnSet monitors for each component that + * the entity no longer has */ + ecs_run_monitors(world, dst_table, src_table->un_set_all, + dst_row, 1, dst_table->un_set_all); + + ecs_run_remove_actions( + world, src_table, src_data, src_row, 1, removed, false); + } + } + + ecs_table_delete(world, src_table, src_data, src_row, false); + + /* If components were added, invoke add actions */ + if (src_table != dst_table || (added && added->count)) { + if (added && (dst_table->flags & EcsTableHasAddActions)) { + ecs_run_add_actions( + world, dst_table, dst_data, dst_row, 1, added, false, true); + } + + /* Run monitors */ + if (dst_table->flags & EcsTableHasMonitors) { + ecs_run_monitors(world, dst_table, dst_table->monitors, dst_row, + 1, src_table->monitors); + } + + /* If removed components were overrides, run OnSet systems for those, as + * the value of those components changed from the removed component to + * the value of component on the base entity */ + if (removed && dst_table->flags & EcsTableHasBase) { + ecs_run_monitors(world, dst_table, src_table->on_set_override, + dst_row, 1, dst_table->on_set_override); + } + } + + info->data = dst_data; + + return dst_row; +} + +static +void delete_entity( + ecs_world_t * world, + ecs_table_t * src_table, + ecs_data_t * src_data, + int32_t src_row, + ecs_entities_t * removed) +{ + if (removed) { + ecs_run_monitors(world, src_table, src_table->un_set_all, + src_row, 1, NULL); + + /* Invoke remove actions before deleting */ + if (src_table->flags & EcsTableHasRemoveActions) { + ecs_run_remove_actions( + world, src_table, src_data, src_row, 1, removed, true); + } + } + + ecs_table_delete(world, src_table, src_data, src_row, true); +} + +/* Updating component monitors is a relatively expensive operation that only + * happens for entities that are monitored. The approach balances the amount of + * processing between the operation on the entity vs the amount of work that + * needs to be done to rematch queries, as a simple brute force approach does + * not scale when there are many tables / queries. Therefore we need to do a bit + * of bookkeeping that is more intelligent than simply flipping a flag */ +static +bool update_component_monitor_w_array( + ecs_world_t *world, + ecs_component_monitor_t * mon, + ecs_entities_t * entities) +{ + bool childof_changed = false; + + if (!entities) { + return false; + } + + int i; + for (i = 0; i < entities->count; i ++) { + ecs_entity_t component = entities->array[i]; + if (component < ECS_HI_COMPONENT_ID) { + ecs_component_monitor_mark(mon, component); + } else if (ECS_HAS_ROLE(component, CHILDOF)) { + childof_changed = true; + } else if (ECS_HAS_ROLE(component, INSTANCEOF)) { + /* If an INSTANCEOF relationship is added to a monitored entity (can + * be either a parent or a base) component monitors need to be + * evaluated for the components of the prefab. */ + ecs_entity_t base = component & ECS_COMPONENT_MASK; + ecs_type_t type = ecs_get_type(world, base); + ecs_entities_t base_entities = ecs_type_to_entities(type); + + /* This evaluates the component monitor for all components of the + * base entity. If the base entity contains INSTANCEOF relationships + * these will be evaluated recursively as well. */ + update_component_monitor_w_array(world, mon, &base_entities); + } + } + + return childof_changed; +} + +static +void update_component_monitors( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entities_t * added, + ecs_entities_t * removed) +{ + bool childof_changed = update_component_monitor_w_array( + world, &world->component_monitors, added); + + childof_changed |= update_component_monitor_w_array( + world, &world->component_monitors, removed); + + /* If this entity is a parent, check if anything changed that could impact + * its place in the hierarchy. If so, we need to mark all of the parent's + * entities as dirty. */ + if (childof_changed && + ecs_map_get(world->child_tables, ecs_vector_t*, entity)) + { + ecs_type_t type = ecs_get_type(world, entity); + ecs_entities_t entities = ecs_type_to_entities(type); + update_component_monitor_w_array(world, + &world->parent_monitors, &entities); + } +} + +static +void commit( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * dst_table, + ecs_entities_t * added, + ecs_entities_t * removed) +{ + ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = info->table; + if (src_table == dst_table) { + /* If source and destination table are the same no action is needed * + * However, if a component was added in the process of traversing a + * table, this suggests that a case switch could have occured. */ + if (((added && added->count) || (removed && removed->count)) && + src_table && src_table->flags & EcsTableHasSwitch) + { + ecs_components_switch( + world, src_table, info->data, info->row, 1, added, removed); + } + + return; + } + + if (src_table) { + ecs_data_t *src_data = info->data; + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (dst_table->type) { + info->row = move_entity(world, entity, info, src_table, + src_data, info->row, dst_table, added, removed); + info->table = dst_table; + } else { + delete_entity( + world, src_table, src_data, info->row, + removed); + + ecs_eis_set(world, entity, &(ecs_record_t){ + NULL, (info->is_watched == true) * -1 + }); + } + } else { + if (dst_table->type) { + info->row = new_entity(world, entity, info, dst_table, added); + info->table = dst_table; + } + } + + /* If the entity is being watched, it is being monitored for changes and + * requires rematching systems when components are added or removed. This + * ensures that systems that rely on components from containers or prefabs + * update the matched tables when the application adds or removes a + * component from, for example, a container. */ + if (info->is_watched) { + update_component_monitors(world, entity, added, removed); + } + + if ((!src_table || !src_table->type) && world->range_check_enabled) { + ecs_assert(!world->stats.max_id || entity <= world->stats.max_id, ECS_OUT_OF_RANGE, 0); + ecs_assert(entity >= world->stats.min_id, ECS_OUT_OF_RANGE, 0); + } +} + +static +void* get_base_component( + ecs_world_t * world, + ecs_stage_t * stage, + ecs_entity_info_t * info, + ecs_entity_t component) +{ + ecs_type_t type = info->table->type; + ecs_entity_t *type_buffer = ecs_vector_first(type, ecs_entity_t); + int32_t p = -1; + void *ptr = NULL; + + while (!ptr && (p = find_prefab(type, p)) != -1) { + ecs_entity_t prefab = type_buffer[p] & ECS_COMPONENT_MASK; + ecs_entity_info_t prefab_info; + if (ecs_get_info(world, prefab, &prefab_info) && prefab_info.table) { + ptr = get_component(&prefab_info, component); + if (!ptr) { + ptr = get_base_component( + world, stage, &prefab_info, component); + } + } + } + + return ptr; +} + +static +void new( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entities_t * to_add) +{ + ecs_entity_info_t info = {0}; + ecs_table_t *table = ecs_table_traverse_add( + world, world->stage.scope_table, to_add, NULL); + new_entity(world, entity, &info, table, to_add); +} + +static +const ecs_entity_t* new_w_data( + ecs_world_t * world, + ecs_table_t * table, + ecs_entities_t * component_ids, + int32_t count, + void ** component_data, + int32_t * row_out) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + int32_t sparse_count = ecs_eis_count(world); + const ecs_entity_t *ids = ecs_sparse_new_ids(world->store.entity_index, count); + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t type = table->type; + + if (!type) { + return ids; + } + + ecs_entities_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = ecs_vector_first(type, ecs_entity_t); + component_array.count = ecs_vector_count(type); + } + + ecs_data_t *data = ecs_table_get_or_create_data(table); + int32_t row = ecs_table_appendn(world, table, data, count, ids); + ecs_entities_t added = ecs_type_to_entities(type); + + /* Update entity index. */ + int i; + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + for (i = 0; i < count; i ++) { + record_ptrs[row + i] = ecs_eis_set(world, ids[i], + &(ecs_record_t){ + .table = table, + .row = row + i + 1 + }); + } + + ecs_defer_none(world, &world->stage); + + ecs_run_add_actions(world, table, data, row, count, &added, + true, component_data == NULL); + + if (component_data) { + /* Set components that we're setting in the component mask so the init + * actions won't call OnSet triggers for them. This ensures we won't + * call OnSet triggers multiple times for the same component */ + int32_t c_i; + for (c_i = 0; c_i < component_ids->count; c_i ++) { + ecs_entity_t c = component_ids->array[c_i]; + + /* Bulk copy column data into new table */ + int32_t table_index = ecs_type_index_of(type, c); + ecs_assert(table_index >= 0, ECS_INTERNAL_ERROR, NULL); + if (table_index >= table->column_count) { + continue; + } + + ecs_column_t *column = &data->columns[table_index]; + int16_t size = column->size; + int16_t alignment = column->alignment; + void *ptr = ecs_vector_first_t(column->data, size, alignment); + ptr = ECS_OFFSET(ptr, size * row); + + /* Copy component data */ + void *src_ptr = component_data[c_i]; + if (!src_ptr) { + continue; + } + + ecs_c_info_t *cdata = get_c_info(world, c); + ecs_copy_t copy; + if (cdata && (copy = cdata->lifecycle.copy)) { + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + copy(world, c, entities, entities, ptr, src_ptr, + ecs_to_size_t(size), count, cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, src_ptr, size * count); + } + }; + + ecs_run_set_systems(world, &added, table, data, row, count, true); + } + + ecs_run_monitors(world, table, table->monitors, row, count, NULL); + + ecs_defer_flush(world, &world->stage); + + if (row_out) { + *row_out = row; + } + + ids = ecs_sparse_ids(world->store.entity_index); + + return &ids[sparse_count]; +} + +static +bool has_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type, + bool match_any, + bool match_prefabs) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return false; + } + + if (!type) { + return true; + } + + ecs_world_t *world_arg = world; + ecs_type_t entity_type = ecs_get_type(world_arg, entity); + + return ecs_type_contains( + world, entity_type, type, match_any, match_prefabs) != 0; +} + +static +void add_remove( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entities_t * to_add, + ecs_entities_t * to_remove) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_add->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_info_t info; + ecs_get_info(world, entity, &info); + + ecs_entity_t add_buffer[ECS_MAX_ADD_REMOVE]; + ecs_entity_t remove_buffer[ECS_MAX_ADD_REMOVE]; + ecs_entities_t added = { .array = add_buffer }; + ecs_entities_t removed = { .array = remove_buffer }; + + ecs_table_t *src_table = info.table; + + ecs_table_t *dst_table = ecs_table_traverse_remove( + world, src_table, to_remove, &removed); + + dst_table = ecs_table_traverse_add( + world, dst_table, to_add, &added); + + commit(world, entity, &info, dst_table, &added, &removed); +} + +static +void add_entities_w_info( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_entities_t * components) +{ + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_entities_t added = { .array = buffer }; + + ecs_table_t *src_table = info->table; + ecs_table_t *dst_table = ecs_table_traverse_add( + world, src_table, components, &added); + + commit(world, entity, info, dst_table, &added, NULL); +} + +static +void remove_entities_w_info( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_entities_t * components) +{ + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_entities_t removed = { .array = buffer }; + + ecs_table_t *src_table = info->table; + ecs_table_t *dst_table = ecs_table_traverse_remove( + world, src_table, components, &removed); + + commit(world, entity, info, dst_table, NULL, &removed); +} + +static +void add_entities( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entities_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + + if (ecs_defer_add(world, stage, entity, components)) { + return; + } + + ecs_entity_info_t info; + ecs_get_info(world, entity, &info); + + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_entities_t added = { .array = buffer }; + + ecs_table_t *src_table = info.table; + ecs_table_t *dst_table = ecs_table_traverse_add( + world, src_table, components, &added); + + commit(world, entity, &info, dst_table, &added, NULL); + + ecs_defer_flush(world, stage); +} + +static +void remove_entities( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entities_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + + if (ecs_defer_remove(world, stage, entity, components)) { + return; + } + + ecs_entity_info_t info; + ecs_get_info(world, entity, &info); + + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_entities_t removed = { .array = buffer }; + + ecs_table_t *src_table = info.table; + ecs_table_t *dst_table = ecs_table_traverse_remove( + world, src_table, components, &removed); + + commit(world, entity, &info, dst_table, NULL, &removed); + + ecs_defer_flush(world, stage); +} + +static +void *get_mutable( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_t component, + ecs_entity_info_t * info, + bool * is_added) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert((component & ECS_COMPONENT_MASK) == component || ECS_HAS_ROLE(component, TRAIT), ECS_INVALID_PARAMETER, NULL); + + void *dst = NULL; + if (ecs_get_info(world, entity, info) && info->table) { + dst = get_component(info, component); + } + + ecs_table_t *table = info->table; + + if (!dst) { + ecs_entities_t to_add = { + .array = &component, + .count = 1 + }; + + add_entities_w_info(world, entity, info, &to_add); + + ecs_get_info(world, entity, info); + ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL); + + dst = get_component(info, component); + + if (is_added) { + *is_added = table != info->table; + } + + return dst; + } else { + if (is_added) { + *is_added = false; + } + + return dst; + } +} + +/* -- Private functions -- */ + +int32_t ecs_record_to_row( + int32_t row, + bool *is_watched_out) +{ + bool is_watched = row < 0; + row = row * -(is_watched * 2 - 1) - 1 * (row != 0); + *is_watched_out = is_watched; + return row; +} + +int32_t ecs_row_to_record( + int32_t row, + bool is_watched) +{ + return (row + 1) * -(is_watched * 2 - 1); +} + +ecs_entities_t ecs_type_to_entities( + ecs_type_t type) +{ + return (ecs_entities_t){ + .array = ecs_vector_first(type, ecs_entity_t), + .count = ecs_vector_count(type) + }; +} + +void ecs_set_watch( + ecs_world_t *world, + ecs_entity_t entity) +{ + (void)world; + + ecs_record_t *record = ecs_eis_get(world, entity); + if (!record) { + ecs_record_t new_record = {.row = -1, .table = NULL}; + ecs_eis_set(world, entity, &new_record); + } else { + if (record->row > 0) { + record->row *= -1; + + } else if (record->row == 0) { + /* If entity is empty, there is no index to change the sign of. In + * this case, set the index to -1, and assign an empty type. */ + record->row = -1; + record->table = NULL; + } + } +} + +ecs_entity_t ecs_find_in_type( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t component, + ecs_entity_t flags) +{ + ecs_vector_each(type, ecs_entity_t, c_ptr, { + ecs_entity_t c = *c_ptr; + + if (flags) { + if ((c & flags) != flags) { + continue; + } + } + + ecs_entity_t e = c & ECS_COMPONENT_MASK; + + if (component) { + ecs_type_t component_type = ecs_get_type(world, e); + if (!ecs_type_has_entity(world, component_type, component)) { + continue; + } + } + + return e; + }); + + return 0; +} + + +/* -- Public functions -- */ + +ecs_entity_t ecs_new_id( + ecs_world_t *world) +{ + ecs_entity_t entity; + + int32_t thread_count = ecs_vector_count(world->workers); + if (thread_count >= 1) { + /* Can't atomically increase number above max int */ + ecs_assert( + world->stats.last_id < UINT_MAX, ECS_INTERNAL_ERROR, NULL); + + entity = (ecs_entity_t)ecs_os_ainc((int32_t*)&world->stats.last_id); + } else { + entity = ecs_eis_recycle(world); + } + + ecs_assert(!world->stats.max_id || entity <= world->stats.max_id, + ECS_OUT_OF_RANGE, NULL); + + return entity; +} + +ecs_entity_t ecs_new_component_id( + ecs_world_t *world) +{ + if (world->in_progress) { + /* Can't issue new id while iterating when in multithreaded mode */ + ecs_assert(ecs_vector_count(world->workers) <= 1, + ECS_INVALID_WHILE_ITERATING, NULL); + } + + if (world->stats.last_component_id >= ECS_HI_COMPONENT_ID) { + /* If the low component ids are depleted, return a regular entity id */ + return ecs_new_id(world); + } else { + return world->stats.last_component_id ++; + } +} + +ecs_entity_t ecs_new_w_type( + ecs_world_t *world, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_entity_t entity = ecs_new_id(world); + + if (type || world->stage.scope) { + ecs_entities_t to_add = ecs_type_to_entities(type); + if (ecs_defer_new(world, stage, entity, &to_add)) { + return entity; + } + new(world, entity, &to_add); + ecs_defer_flush(world, stage); + } else { + ecs_eis_set(world, entity, &(ecs_record_t){ 0 }); + } + + return entity; +} + +ecs_entity_t ecs_new_w_entity( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_entity_t entity = ecs_new_id(world); + + if (component || stage->scope) { + ecs_entities_t to_add = { + .array = &component, + .count = 1 + }; + + if (ecs_defer_new(world, stage, entity, &to_add)) { + return entity; + } + + ecs_entity_t old_scope = 0; + if (ECS_HAS_ROLE(component, CHILDOF)) { + old_scope = ecs_set_scope(world, 0); + } + + new(world, entity, &to_add); + + if (ECS_HAS_ROLE(component, CHILDOF)) { + ecs_set_scope(world, old_scope); + } + + ecs_defer_flush(world, stage); + } else { + ecs_eis_set(world, entity, &(ecs_record_t){ 0 }); + } + + return entity; +} + +const ecs_entity_t* ecs_bulk_new_w_data( + ecs_world_t *world, + int32_t count, + ecs_entities_t * components, + void * data) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + const ecs_entity_t *ids; + if (ecs_defer_bulk_new(world, stage, count, components, data, &ids)) { + return ids; + } + ecs_type_t type = ecs_type_find(world, components->array, components->count); + ecs_table_t *table = ecs_table_from_type(world, type); + ids = new_w_data(world, table, NULL, count, data, NULL); + ecs_defer_flush(world, stage); + return ids; +} + +const ecs_entity_t* ecs_bulk_new_w_type( + ecs_world_t *world, + ecs_type_t type, + int32_t count) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + const ecs_entity_t *ids; + ecs_entities_t components = ecs_type_to_entities(type); + if (ecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) { + return ids; + } + ecs_table_t *table = ecs_table_from_type(world, type); + ids = new_w_data(world, table, NULL, count, NULL, NULL); + ecs_defer_flush(world, stage); + return ids; +} + +const ecs_entity_t* ecs_bulk_new_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + int32_t count) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_entities_t components = { + .array = &entity, + .count = 1 + }; + const ecs_entity_t *ids; + if (ecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) { + return ids; + } + ecs_table_t *table = ecs_table_find_or_create(world, &components); + ids = new_w_data(world, table, NULL, count, NULL, NULL); + ecs_defer_flush(world, stage); + return ids; +} + +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = ecs_get_stage(&world); + if (ecs_defer_clear(world, stage, entity)) { + return; + } + + ecs_entity_info_t info; + info.table = NULL; + + ecs_get_info(world, entity, &info); + + ecs_table_t *table = info.table; + if (table) { + ecs_type_t type = table->type; + + /* Remove all components */ + ecs_entities_t to_remove = ecs_type_to_entities(type); + remove_entities_w_info(world, entity, &info, &to_remove); + } + + ecs_defer_flush(world, stage); +} + +void ecs_delete_children( + ecs_world_t *world, + ecs_entity_t parent) +{ + ecs_vector_t *child_tables = ecs_map_get_ptr( + world->child_tables, ecs_vector_t*, parent); + + if (child_tables) { + ecs_table_t **tables = ecs_vector_first(child_tables, ecs_table_t*); + int32_t i, count = ecs_vector_count(child_tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = tables[i]; + + /* Recursively delete entities of children */ + ecs_data_t *data = ecs_table_get_data(table); + if (data) { + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + int32_t child, child_count = ecs_vector_count(data->entities); + for (child = 0; child < child_count; child ++) { + ecs_delete_children(world, entities[child]); + } + } + + /* Clear components from table (invokes destructors, OnRemove) */ + ecs_table_clear(world, table); + + /* Delete table */ + ecs_delete_table(world, table); + }; + + ecs_vector_free(child_tables); + } + + ecs_map_remove(world->child_tables, parent); +} + +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = ecs_get_stage(&world); + if (ecs_defer_delete(world, stage, entity)) { + return; + } + + ecs_record_t *r = ecs_sparse_remove_get( + world->store.entity_index, ecs_record_t, entity); + if (r) { + ecs_entity_info_t info; + set_info_from_record(entity, &info, r); + if (info.is_watched) { + ecs_delete_children(world, entity); + } + + /* If entity has components, remove them */ + ecs_table_t *table = info.table; + if (table) { + ecs_type_t type = table->type; + ecs_entities_t to_remove = ecs_type_to_entities(type); + delete_entity(world, table, info.data, info.row, &to_remove); + r->table = NULL; + } + r->row = 0; + } + + ecs_defer_flush(world, stage); +} + +void ecs_add_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_entities_t components = ecs_type_to_entities(type); + add_entities(world, entity, &components); +} + +void ecs_add_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t to_add) +{ + ecs_entities_t components = { .array = &to_add, .count = 1 }; + add_entities(world, entity, &components); +} + +void ecs_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_entities_t components = ecs_type_to_entities(type); + remove_entities(world, entity, &components); +} + +void ecs_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t to_remove) +{ + ecs_entities_t components = { .array = &to_remove, .count = 1 }; + remove_entities(world, entity, &components); +} + +void ecs_add_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t to_add, + ecs_entity_t to_remove) +{ + ecs_entities_t components_add = { .array = &to_add, .count = 1 }; + ecs_entities_t components_remove = { .array = &to_remove, .count = 1 }; + add_remove(world, entity, &components_add, &components_remove); +} + +void ecs_add_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t to_add, + ecs_type_t to_remove) +{ + ecs_entities_t components_add = ecs_type_to_entities(to_add); + ecs_entities_t components_remove = ecs_type_to_entities(to_remove); + add_remove(world, entity, &components_add, &components_remove); +} + +ecs_entity_t ecs_clone( + ecs_world_t *world, + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + + if (!dst) { + dst = ecs_new_id(world); + } + + if (ecs_defer_clone(world, stage, dst, src, copy_value)) { + return dst; + } + + ecs_entity_info_t src_info; + bool found = ecs_get_info(world, src, &src_info); + ecs_table_t *src_table = src_info.table; + + if (!found || !src_table) { + return dst; + } + + ecs_type_t src_type = src_table->type; + ecs_entities_t to_add = ecs_type_to_entities(src_type); + + ecs_entity_info_t dst_info = {0}; + dst_info.row = new_entity(world, dst, &dst_info, src_table, &to_add); + + if (copy_value) { + ecs_table_move(world, dst, src, src_table, dst_info.data, + dst_info.row, src_table, src_info.data, src_info.row); + + int i; + for (i = 0; i < to_add.count; i ++) { + ecs_run_set_systems(world, &to_add, + src_table, src_info.data, dst_info.row, 1, true); + } + } + + ecs_defer_flush(world, stage); + + return dst; +} + +const void* ecs_get_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_entity_info_t info; + void *ptr = NULL; + + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + bool found = ecs_get_info(world, entity, &info); + if (found) { + if (!info.table) { + return NULL; + } + + ptr = get_component(&info, component); + if (!ptr) { + if (component != ecs_typeid(EcsName) && component != EcsPrefab) { + ptr = get_base_component( + world, stage, &info, component); + } + } + } + + return ptr; +} + +const void* ecs_get_ref_w_entity( + ecs_world_t * world, + ecs_ref_t * ref, + ecs_entity_t entity, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!entity || !ref->entity || entity == ref->entity, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!component || !ref->component || component == ref->component, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *record = ref->record; + + entity |= ref->entity; + + if (!record) { + record = ecs_eis_get(world, entity); + } + + if (!record || !record->table) { + return NULL; + } + + ecs_table_t *table = record->table; + + if (ref->record == record && + ref->table == table && + ref->row == record->row && + ref->alloc_count == table->alloc_count) + { + return ref->ptr; + } + + component |= ref->component; + + ref->entity = entity; + ref->component = component; + ref->table = table; + ref->row = record->row; + ref->alloc_count = table->alloc_count; + + ecs_entity_info_t info = {0}; + set_info_from_record(entity, &info, record); + ref->ptr = get_component(&info, component); + ref->record = record; + + return ref->ptr; +} + +void* ecs_get_mut_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + bool * is_added) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + void *result; + + if (ecs_defer_set( + world, stage, EcsOpMut, entity, component, 0, NULL, &result, is_added)) + { + return result; + } + + ecs_entity_info_t info; + result = get_mutable(world, entity, component, &info, is_added); + + ecs_defer_flush(world, stage); + + return result; +} + +void ecs_modified_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + + if (ecs_defer_modified(world, stage, entity, component)) { + return; + } + + /* If the entity does not have the component, calling ecs_modified is + * invalid. The assert needs to happen after the defer statement, as the + * entity may not have the component when this function is called while + * operations are being deferred. */ + ecs_assert(ecs_has_entity(world, entity, component), + ECS_INVALID_PARAMETER, NULL); + + ecs_entity_info_t info = {0}; + if (ecs_get_info(world, entity, &info)) { + ecs_entities_t added = { + .array = &component, + .count = 1 + }; + ecs_run_set_systems(world, &added, + info.table, info.data, info.row, 1, false); + } + + ecs_defer_flush(world, stage); +} + +static +ecs_entity_t assign_ptr_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + size_t size, + void * ptr, + bool is_move, + bool notify) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + + ecs_entities_t added = { + .array = &component, + .count = 1 + }; + + if (!entity) { + entity = ecs_new_id(world); + ecs_entity_t scope = stage->scope; + if (scope) { + ecs_add_entity(world, entity, ECS_CHILDOF | scope); + } + } + + if (ecs_defer_set(world, stage, EcsOpSet, entity, component, + ecs_from_size_t(size), ptr, NULL, NULL)) + { + return entity; + } + + ecs_entity_info_t info; + + void *dst = get_mutable(world, entity, component, &info, NULL); + + /* This can no longer happen since we defer operations */ + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ptr) { + ecs_entity_t real_id = ecs_get_typeid(world, component); + ecs_c_info_t *cdata = get_c_info(world, real_id); + if (cdata) { + if (is_move) { + ecs_move_t move = cdata->lifecycle.move; + if (move) { + move(world, real_id, &entity, &entity, dst, ptr, size, 1, + cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, ptr, ecs_from_size_t(size)); + } + } else { + ecs_copy_t copy = cdata->lifecycle.copy; + if (copy) { + copy(world, real_id, &entity, &entity, dst, ptr, size, 1, + cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, ptr, ecs_from_size_t(size)); + } + } + } else { + ecs_os_memcpy(dst, ptr, ecs_from_size_t(size)); + } + } else { + memset(dst, 0, size); + } + + ecs_table_mark_dirty(info.table, component); + + if (notify) { + ecs_run_set_systems(world, &added, + info.table, info.data, info.row, 1, false); + } + + ecs_defer_flush(world, stage); + + return entity; +} + +ecs_entity_t ecs_set_ptr_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + size_t size, + const void *ptr) +{ + /* Safe to cast away const: function won't modify if move arg is false */ + return assign_ptr_w_entity( + world, entity, component, size, (void*)ptr, false, true); +} + +ecs_entity_t ecs_get_case( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t sw_id) +{ + ecs_entity_info_t info; + ecs_table_t *table; + if (!ecs_get_info(world, entity, &info) || !(table = info.table)) { + return 0; + } + + sw_id = sw_id | ECS_SWITCH; + + ecs_type_t type = table->type; + int32_t index = ecs_type_index_of(type, sw_id); + if (index == -1) { + return 0; + } + + index -= table->sw_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = info.data->sw_columns[index].data; + return ecs_switch_get(sw, info.row); +} + +void ecs_enable_component_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + bool enable) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + + if (ecs_defer_enable( + world, stage, entity, component, enable)) + { + return; + } else { + /* Operations invoked by enable/disable should not be deferred */ + stage->defer --; + } + + ecs_entity_info_t info; + ecs_get_info(world, entity, &info); + + ecs_entity_t bs_id = (component & ECS_COMPONENT_MASK) | ECS_DISABLED; + + ecs_table_t *table = info.table; + int32_t index = -1; + if (table) { + index = ecs_type_index_of(table->type, bs_id); + } + + if (index == -1) { + ecs_add_entity(world, entity, bs_id); + ecs_enable_component_w_entity(world, entity, component, enable); + return; + } + + index -= table->bs_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &info.data->bs_columns[index].data; + ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_bitset_set(bs, info.row, enable); +} + +bool ecs_is_component_enabled_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component) +{ + ecs_entity_info_t info; + ecs_table_t *table; + if (!ecs_get_info(world, entity, &info) || !(table = info.table)) { + return false; + } + + ecs_entity_t bs_id = (component & ECS_COMPONENT_MASK) | ECS_DISABLED; + + ecs_type_t type = table->type; + int32_t index = ecs_type_index_of(type, bs_id); + if (index == -1) { + /* If table does not have DISABLED column for component, component is + * always enabled, if the entity has it */ + return ecs_has_entity(world, entity, component); + } + + index -= table->bs_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &info.data->bs_columns[index].data; + + return ecs_bitset_get(bs, info.row); +} + +bool ecs_has_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component) +{ + if (ECS_HAS_ROLE(component, CASE)) { + ecs_entity_info_t info; + ecs_table_t *table; + if (!ecs_get_info(world, entity, &info) || !(table = info.table)) { + return false; + } + + int32_t index = ecs_table_switch_from_case(world, table, component); + ecs_assert(index < table->sw_column_count, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = info.data; + ecs_switch_t *sw = data->sw_columns[index].data; + ecs_entity_t value = ecs_switch_get(sw, info.row); + + return value == (component & ECS_COMPONENT_MASK); + } else { + ecs_type_t type = ecs_get_type(world, entity); + return ecs_type_has_entity(world, type, component); + } +} + +bool ecs_has_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + return has_type(world, entity, type, true, true); +} + +ecs_entity_t ecs_get_parent_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component) +{ + ecs_type_t type = ecs_get_type(world, entity); + ecs_entity_t parent = ecs_find_in_type(world, type, component, ECS_CHILDOF); + return parent; +} + +const char* ecs_get_name( + ecs_world_t *world, + ecs_entity_t entity) +{ + if (entity == EcsSingleton) { + return "$"; + } + + const EcsName *id = ecs_get(world, entity, EcsName); + + if (id) { + return id->value; + } else { + return NULL; + } +} + +ecs_type_t ecs_type_from_entity( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return NULL; + } + + const EcsType *type = ecs_get(world, entity, EcsType); + if (type) { + return type->normalized; + } + + return ecs_type_find(world, &entity, 1); +} + +ecs_entity_t ecs_type_to_entity( + ecs_world_t *world, + ecs_type_t type) +{ + (void)world; + + if (!type) { + return 0; + } + + /* If array contains n entities, it cannot be reduced to a single entity */ + if (ecs_vector_count(type) != 1) { + ecs_abort(ECS_TYPE_NOT_AN_ENTITY, NULL); + } + + return *(ecs_vector_first(type, ecs_entity_t)); +} + +bool ecs_is_alive( + ecs_world_t *world, + ecs_entity_t e) +{ + return ecs_eis_is_alive(world, e); +} + +bool ecs_exists( + ecs_world_t *world, + ecs_entity_t e) +{ + return ecs_eis_exists(world, e); +} + +ecs_type_t ecs_get_type( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *record = NULL; + + record = ecs_eis_get(world, entity); + + ecs_table_t *table; + if (record && (table = record->table)) { + return table->type; + } + + return NULL; +} + +ecs_entity_t ecs_get_typeid( + ecs_world_t *world, + ecs_entity_t e) +{ + if (ECS_HAS_ROLE(e, TRAIT)) { + ecs_entity_t trait = ecs_entity_t_hi(e & ECS_COMPONENT_MASK); + if (ecs_has(world, trait, EcsComponent)) { + /* This is not a trait tag, trait is the value */ + return trait; + } else { + /* This is a trait tag, component is the value */ + return ecs_entity_t_lo(e); + } + } else if (e & ECS_ROLE_MASK) { + return 0; + } + + return e; +} + +int32_t ecs_count_type( + ecs_world_t *world, + ecs_type_t type) +{ + if (!type) { + return 0; + } + + return ecs_count_w_filter(world, &(ecs_filter_t){ + .include = type + }); +} + +int32_t ecs_count_entity( + ecs_world_t *world, + ecs_entity_t entity) +{ + if (!entity) { + return 0; + } + + /* Get temporary type that just contains entity */ + ECS_VECTOR_STACK(type, ecs_entity_t, &entity, 1); + + return ecs_count_w_filter(world, &(ecs_filter_t){ + .include = type + }); +} + +int32_t ecs_count_w_filter( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = ecs_sparse_count(tables); + int32_t result = 0; + + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i); + if (!filter || ecs_table_match_filter(world, table, filter)) { + result += ecs_table_count(table); + } + } + + return result; +} + +bool ecs_defer_begin( + ecs_world_t *world) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + + if (world->in_progress) { + return ecs_stage_defer_begin(world, stage); + } else { + return ecs_defer_none(world, stage); + } +} + +bool ecs_defer_end( + ecs_world_t *world) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + + if (world->in_progress) { + return ecs_stage_defer_end(world, stage); + } else { + return ecs_defer_flush(world, stage); + } +} + +static +size_t append_to_str( + char **buffer, + const char *str, + size_t bytes_left, + size_t *required) +{ + char *ptr = *buffer; + + size_t len = strlen(str); + size_t to_write; + if (bytes_left < len) { + to_write = bytes_left; + bytes_left = 0; + } else { + to_write = len; + bytes_left -= len; + } + + if (to_write) { + ecs_os_memcpy(ptr, str, to_write); + } + + (*required) += len; + (*buffer) += to_write; + + return bytes_left; +} + +const char* ecs_role_str( + ecs_entity_t entity) +{ + if (ECS_HAS_ROLE(entity, CHILDOF)) { + return "CHILDOF"; + } else + if (ECS_HAS_ROLE(entity, INSTANCEOF)) { + return "INSTANCEOF"; + } else + if (ECS_HAS_ROLE(entity, TRAIT)) { + return "TRAIT"; + } else + if (ECS_HAS_ROLE(entity, DISABLED)) { + return "DISABLED"; + } else + if (ECS_HAS_ROLE(entity, XOR)) { + return "XOR"; + } else + if (ECS_HAS_ROLE(entity, OR)) { + return "OR"; + } else + if (ECS_HAS_ROLE(entity, AND)) { + return "AND"; + } else + if (ECS_HAS_ROLE(entity, NOT)) { + return "NOT"; + } else + if (ECS_HAS_ROLE(entity, SWITCH)) { + return "SWITCH"; + } else + if (ECS_HAS_ROLE(entity, CASE)) { + return "CASE"; + } else + if (ECS_HAS_ROLE(entity, OWNED)) { + return "OWNED"; + } else { + return "UNKNOWN"; + } +} + +size_t ecs_entity_str( + ecs_world_t *world, + ecs_entity_t entity, + char *buffer, + size_t buffer_len) +{ + char *ptr = buffer; + size_t bytes_left = buffer_len - 1, required = 0; + if (entity & ECS_ROLE_MASK) { + const char *role = ecs_role_str(entity); + bytes_left = append_to_str(&ptr, role, bytes_left, &required); + bytes_left = append_to_str(&ptr, "|", bytes_left, &required); + } + + ecs_entity_t e = entity & ECS_COMPONENT_MASK; + if (ECS_HAS_ROLE(entity, TRAIT)) { + ecs_entity_t lo = ecs_entity_t_lo(e); + ecs_entity_t hi = ecs_entity_t_hi(e); + + if (hi) { + char *hi_path = ecs_get_fullpath(world, hi); + bytes_left = append_to_str(&ptr, hi_path, bytes_left, &required); + ecs_os_free(hi_path); + bytes_left = append_to_str(&ptr, ">", bytes_left, &required); + } + char *lo_path = ecs_get_fullpath(world, lo); + bytes_left = append_to_str(&ptr, lo_path, bytes_left, &required); + ecs_os_free(lo_path); + } else { + char *path = ecs_get_fullpath(world, e); + bytes_left = append_to_str(&ptr, path, bytes_left, &required); + ecs_os_free(path); + } + + ptr[0] = '\0'; + return required; +} + +static +void flush_bulk_new( + ecs_world_t * world, + ecs_op_t * op) +{ + ecs_entity_t *ids = op->is._n.entities; + void **bulk_data = op->is._n.bulk_data; + if (bulk_data) { + ecs_entity_t *components = op->components.array; + int c, c_count = op->components.count; + for (c = 0; c < c_count; c ++) { + ecs_entity_t component = components[c]; + const EcsComponent *cptr = ecs_get(world, component, EcsComponent); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + size_t size = ecs_to_size_t(cptr->size); + void *ptr, *data = bulk_data[c]; + int i, count = op->is._n.count; + for (i = 0, ptr = data; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { + assign_ptr_w_entity(world, ids[i], component, size, ptr, + true, true); + } + ecs_os_free(data); + } + ecs_os_free(bulk_data); + } else { + int i, count = op->is._n.count; + for (i = 0; i < count; i ++) { + add_entities(world, ids[i], &op->components); + } + } + + if (op->components.count > 1) { + ecs_os_free(op->components.array); + } + + ecs_os_free(ids); +} + +static +void discard_op( + ecs_op_t * op) +{ + ecs_assert(op->kind != EcsOpBulkNew, ECS_INTERNAL_ERROR, NULL); + + void *value = op->is._1.value; + if (value) { + ecs_os_free(value); + } + + ecs_entity_t *components = op->components.array; + if (components) { + ecs_os_free(components); + } +} + +static +bool valid_components( + ecs_world_t * world, + ecs_entities_t * entities) +{ + ecs_entity_t *array = entities->array; + int32_t i, count = entities->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, CHILDOF)) { + e &= ECS_COMPONENT_MASK; + if (ecs_exists(world, e) && !ecs_is_alive(world, e)) { + return false; + } + } + } + return true; +} + +/* Leave safe section. Run all deferred commands. */ +bool ecs_defer_flush( + ecs_world_t * world, + ecs_stage_t * stage) +{ + if (!--stage->defer) { + ecs_vector_t *defer_queue = stage->defer_queue; + stage->defer_queue = NULL; + if (defer_queue) { + ecs_op_t *ops = ecs_vector_first(defer_queue, ecs_op_t); + int32_t i, count = ecs_vector_count(defer_queue); + + for (i = 0; i < count; i ++) { + ecs_op_t *op = &ops[i]; + ecs_entity_t e = op->is._1.entity; + if (op->kind == EcsOpBulkNew) { + e = 0; + } + + /* If entity is no longer alive, this could be because the queue + * contained both a delete and a subsequent add/remove/set which + * should be ignored. */ + if (e && !ecs_is_alive(world, e) && ecs_eis_exists(world, e)) { + ecs_assert(op->kind != EcsOpNew && op->kind != EcsOpClone, + ECS_INTERNAL_ERROR, NULL); + world->discard_count ++; + discard_op(op); + continue; + } + + if (op->components.count == 1) { + op->components.array = &op->component; + } + + switch(op->kind) { + case EcsOpNew: + if (op->scope) { + ecs_add_entity(world, e, ECS_CHILDOF | op->scope); + } + /* Fallthrough */ + case EcsOpAdd: + if (valid_components(world, &op->components)) { + world->add_count ++; + add_entities(world, e, &op->components); + } else { + ecs_delete(world, e); + } + break; + case EcsOpRemove: + remove_entities(world, e, &op->components); + break; + case EcsOpClone: + ecs_clone(world, e, op->component, op->is._1.clone_value); + break; + case EcsOpSet: + assign_ptr_w_entity(world, e, + op->component, ecs_to_size_t(op->is._1.size), + op->is._1.value, true, true); + break; + case EcsOpMut: + assign_ptr_w_entity(world, e, + op->component, ecs_to_size_t(op->is._1.size), + op->is._1.value, true, false); + break; + case EcsOpModified: + ecs_modified_w_entity(world, e, op->component); + break; + case EcsOpDelete: { + ecs_delete(world, e); + break; + } + case EcsOpEnable: + ecs_enable_component_w_entity( + world, e, op->component, true); + break; + case EcsOpDisable: + ecs_enable_component_w_entity( + world, e, op->component, false); + break; + case EcsOpClear: + ecs_clear(world, e); + break; + case EcsOpBulkNew: + flush_bulk_new(world, op); + + /* Continue since flush_bulk_new is repsonsible for cleaning + * up resources. */ + continue; + } + + if (op->components.count > 1) { + ecs_os_free(op->components.array); + } + + if (op->is._1.value) { + ecs_os_free(op->is._1.value); + } + }; + + if (defer_queue != stage->defer_merge_queue) { + ecs_vector_free(defer_queue); + } + } + + return true; + } + + return false; +} + +static +ecs_op_t* new_defer_op(ecs_stage_t *stage) { + ecs_op_t *result = ecs_vector_add(&stage->defer_queue, ecs_op_t); + ecs_os_memset(result, 0, ECS_SIZEOF(ecs_op_t)); + return result; +} + +static +void new_defer_component_ids( + ecs_op_t *op, + ecs_entities_t *components) +{ + int32_t components_count = components->count; + if (components_count == 1) { + ecs_entity_t component = components->array[0]; + op->component = component; + op->components = (ecs_entities_t) { + .array = NULL, + .count = 1 + }; + } else if (components_count) { + ecs_size_t array_size = components_count * ECS_SIZEOF(ecs_entity_t); + op->components.array = ecs_os_malloc(array_size); + ecs_os_memcpy(op->components.array, components->array, array_size); + op->components.count = components_count; + } else { + op->component = 0; + op->components = (ecs_entities_t){ 0 }; + } +} + +static +bool defer_add_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entities_t *components) +{ + if (stage->defer) { + ecs_entity_t scope = stage->scope; + if (components) { + if (!components->count && !scope) { + return true; + } + } + + ecs_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->scope = scope; + op->is._1.entity = entity; + + new_defer_component_ids(op, components); + + if (op_kind == EcsOpNew) { + world->new_count ++; + } else if (op_kind == EcsOpAdd) { + world->add_count ++; + } else if (op_kind == EcsOpRemove) { + world->remove_count ++; + } + + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool ecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + return (++ stage->defer) == 1; +} + +bool ecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpModified; + op->component = component; + op->is._1.entity = entity; + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool ecs_defer_clone( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpClone; + op->component = src; + op->is._1.entity = entity; + op->is._1.clone_value = clone_value; + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool ecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpDelete; + op->is._1.entity = entity; + world->delete_count ++; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool ecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpClear; + op->is._1.entity = entity; + world->clear_count ++; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool ecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = enable ? EcsOpEnable : EcsOpDisable; + op->is._1.entity = entity; + op->component = component; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool ecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_entities_t *components_ids, + void **component_data, + const ecs_entity_t **ids_out) +{ + if (stage->defer) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); + void **defer_data = NULL; + + world->bulk_new_count ++; + + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new_id(world); + } + + /* Create private copy for component data */ + if (component_data) { + int c, c_count = components_ids->count; + ecs_entity_t *components = components_ids->array; + defer_data = ecs_os_malloc(ECS_SIZEOF(void*) * c_count); + for (c = 0; c < c_count; c ++) { + ecs_entity_t comp = components[c]; + const EcsComponent *cptr = ecs_get(world, comp, EcsComponent); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t size = cptr->size; + void *data = ecs_os_malloc(size * count); + defer_data[c] = data; + + ecs_c_info_t *cinfo = NULL; + ecs_entity_t real_id = ecs_get_typeid(world, comp); + if (real_id) { + cinfo = ecs_get_c_info(world, real_id); + } + ecs_xtor_t ctor; + if (cinfo && (ctor = cinfo->lifecycle.ctor)) { + void *ctx = cinfo->lifecycle.ctx; + ctor(world, comp, ids, data, ecs_to_size_t(size), count, ctx); + ecs_move_t move; + if ((move = cinfo->lifecycle.move)) { + move(world, comp, ids, ids, data, component_data[c], + ecs_to_size_t(size), count, ctx); + } else { + ecs_os_memcpy(data, component_data[c], size * count); + } + } else { + ecs_os_memcpy(data, component_data[c], size * count); + } + } + } + + /* Store data in op */ + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpBulkNew; + op->is._n.entities = ids; + op->is._n.bulk_data = defer_data; + op->is._n.count = count; + new_defer_component_ids(op, components_ids); + *ids_out = ids; + + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool ecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entities_t *components) +{ + return defer_add_remove(world, stage, EcsOpNew, entity, components); +} + +bool ecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entities_t *components) +{ + return defer_add_remove(world, stage, EcsOpAdd, entity, components); +} + +bool ecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entities_t *components) +{ + return defer_add_remove(world, stage, EcsOpRemove, entity, components); +} + +bool ecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added) +{ + if (stage->defer) { + world->set_count ++; + if (!size) { + const EcsComponent *cptr = ecs_get(world, component, EcsComponent); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + size = cptr->size; + } + + ecs_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->component = component; + op->is._1.entity = entity; + op->is._1.size = size; + op->is._1.value = ecs_os_malloc(size); + + if (!value) { + value = ecs_get_w_entity(world, entity, component); + if (is_added) { + *is_added = value == NULL; + } + } + + ecs_c_info_t *c_info = NULL; + ecs_entity_t real_id = ecs_get_typeid(world, component); + if (real_id) { + c_info = ecs_get_c_info(world, real_id); + } + ecs_xtor_t ctor; + if (c_info && (ctor = c_info->lifecycle.ctor)) { + ctor(world, component, &entity, op->is._1.value, + ecs_to_size_t(size), 1, c_info->lifecycle.ctx); + + ecs_copy_t copy; + if (value) { + if ((copy = c_info->lifecycle.copy)) { + copy(world, component, &entity, &entity, op->is._1.value, value, + ecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(op->is._1.value, value, size); + } + } + } else if (value) { + ecs_os_memcpy(op->is._1.value, value, size); + } + + if (value_out) { + *value_out = op->is._1.value; + } + + return true; + } else { + stage->defer ++; + } + + return false; +} + +void ecs_stage_merge( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_assert(stage != &world->stage, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(stage->defer == 0, ECS_INVALID_PARAMETER, NULL); + if (ecs_vector_count(stage->defer_merge_queue)) { + stage->defer ++; + stage->defer_queue = stage->defer_merge_queue; + ecs_defer_flush(world, stage); + ecs_vector_clear(stage->defer_merge_queue); + ecs_assert(stage->defer_queue == NULL, ECS_INVALID_PARAMETER, NULL); + } +} + +bool ecs_stage_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + if (ecs_defer_none(world, stage)) { + stage->defer_queue = stage->defer_merge_queue; + return true; + } + return false; +} + +bool ecs_stage_defer_end( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + stage->defer --; + if (!stage->defer) { + stage->defer_merge_queue = stage->defer_queue; + stage->defer_queue = NULL; + return true; + } + return false; +} + +void ecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) +{ + /* Execute post frame actions */ + ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { + action->action(world, action->ctx); + }); + + ecs_vector_free(stage->post_frame_actions); + stage->post_frame_actions = NULL; +} + +void ecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage) +{ + memset(stage, 0, sizeof(ecs_stage_t)); + if (stage == &world->stage) { + stage->id = 0; + } else if (stage == &world->temp_stage) { + stage->id = 1; + } +} + +void ecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + ecs_vector_free(stage->defer_queue); + ecs_vector_free(stage->defer_merge_queue); +} + + +/** Resize the vector buffer */ +static +ecs_vector_t* resize( + ecs_vector_t *vector, + int16_t offset, + int32_t size) +{ + ecs_vector_t *result = ecs_os_realloc(vector, offset + size); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); + return result; +} + +/* -- Public functions -- */ + +ecs_vector_t* _ecs_vector_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->count = 0; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; +#endif + return result; +} + +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); + + result->count = elem_count; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; +#endif + return result; +} + +void ecs_vector_free( + ecs_vector_t *vector) +{ + ecs_os_free(vector); +} + +void ecs_vector_clear( + ecs_vector_t *vector) +{ + if (vector) { + vector->count = 0; + } +} + +void _ecs_vector_zero( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + void *array = ECS_OFFSET(vector, offset); + ecs_os_memset(array, 0, elem_size * vector->count); +} + +void ecs_vector_assert_size( + ecs_vector_t *vector, + ecs_size_t elem_size) +{ + (void)elem_size; + + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + } +} + +void* _ecs_vector_addn( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); + + if (elem_count == 1) { + return _ecs_vector_add(array_inout, elem_size, offset); + } + + ecs_vector_t *vector = *array_inout; + if (!vector) { + vector = _ecs_vector_new(elem_size, offset, 1); + *array_inout = vector; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t max_count = vector->size; + int32_t old_count = vector->count; + int32_t new_count = old_count + elem_count; + + if ((new_count - 1) >= max_count) { + if (!max_count) { + max_count = elem_count; + } else { + while (max_count < new_count) { + max_count *= 2; + } + } + + vector = resize(vector, offset, max_count * elem_size); + vector->size = max_count; + *array_inout = vector; + } + + vector->count = new_count; + + return ECS_OFFSET(vector, offset + elem_size * old_count); +} + +void* _ecs_vector_add( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) +{ + ecs_vector_t *vector = *array_inout; + int32_t count, size; + + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + count = vector->count; + size = vector->size; + + if (count >= size) { + size *= 2; + if (!size) { + size = 2; + } + vector = resize(vector, offset, size * elem_size); + *array_inout = vector; + vector->size = size; + } + + vector->count = count + 1; + return ECS_OFFSET(vector, offset + elem_size * count); + } + + vector = _ecs_vector_new(elem_size, offset, 2); + *array_inout = vector; + vector->count = 1; + vector->size = 2; + return ECS_OFFSET(vector, offset); +} + +int32_t _ecs_vector_move_index( + ecs_vector_t **dst, + ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + ecs_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + void *dst_elem = _ecs_vector_add(dst, elem_size, offset); + void *src_elem = _ecs_vector_get(src, elem_size, offset, index); + + ecs_os_memcpy(dst_elem, src_elem, elem_size); + return _ecs_vector_remove_index(src, elem_size, offset, index); +} + +void ecs_vector_remove_last( + ecs_vector_t *vector) +{ + if (vector && vector->count) vector->count --; +} + +bool _ecs_vector_pop( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + void *value) +{ + if (!vector) { + return false; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + if (!count) { + return false; + } + + void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size); + + if (value) { + ecs_os_memcpy(value, elem, elem_size); + } + + ecs_vector_remove_last(vector); + + return true; +} + +int32_t _ecs_vector_remove_index( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + void *elem = ECS_OFFSET(buffer, index * elem_size); + + ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL); + + count --; + if (index != count) { + void *last_elem = ECS_OFFSET(buffer, elem_size * count); + ecs_os_memcpy(elem, last_elem, elem_size); + } + + vector->count = count; + + return count; +} + +void _ecs_vector_reclaim( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) +{ + ecs_vector_t *vector = *array_inout; + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t size = vector->size; + int32_t count = vector->count; + + if (count < size) { + size = count; + vector = resize(vector, offset, size * elem_size); + vector->size = size; + *array_inout = vector; + } +} + +int32_t ecs_vector_count( + const ecs_vector_t *vector) +{ + if (!vector) { + return 0; + } + return vector->count; +} + +int32_t ecs_vector_size( + const ecs_vector_t *vector) +{ + if (!vector) { + return 0; + } + return vector->size; +} + +int32_t _ecs_vector_set_size( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_vector_t *vector = *array_inout; + + if (!vector) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + return elem_count; + } else { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t result = vector->size; + + if (elem_count < vector->count) { + elem_count = vector->count; + } + + if (result < elem_count) { + elem_count = ecs_next_pow_of_2(elem_count); + vector = resize(vector, offset, elem_count * elem_size); + vector->size = elem_count; + *array_inout = vector; + result = elem_count; + } + + return result; + } +} + +int32_t _ecs_vector_grow( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + int32_t current = ecs_vector_count(*array_inout); + return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count); +} + +int32_t _ecs_vector_set_count( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + if (!*array_inout) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + } + + ecs_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + (*array_inout)->count = elem_count; + ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count); + return size; +} + +void* _ecs_vector_first( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + (void)elem_size; + + ecs_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + if (vector && vector->size) { + return ECS_OFFSET(vector, offset); + } else { + return NULL; + } +} + +void* _ecs_vector_get( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + if (!vector) { + return NULL; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + + if (index >= count) { + return NULL; + } + + return ECS_OFFSET(vector, offset + elem_size * index); +} + +void* _ecs_vector_last( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + int32_t count = vector->count; + if (!count) { + return NULL; + } else { + return ECS_OFFSET(vector, offset + elem_size * (count - 1)); + } + } else { + return NULL; + } +} + +int32_t _ecs_vector_set_min_size( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + if (!*vector_inout || (*vector_inout)->size < elem_count) { + return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count); + } else { + return (*vector_inout)->size; + } +} + +int32_t _ecs_vector_set_min_count( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + _ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count); + + ecs_vector_t *v = *vector_inout; + if (v && v->count < elem_count) { + v->count = elem_count; + } + + return v->count; +} + +void _ecs_vector_sort( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + ecs_comparator_t compare_action) +{ + if (!vector) { + return; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + + if (count > 1) { + qsort(buffer, (size_t)count, (size_t)elem_size, compare_action); + } +} + +void _ecs_vector_memory( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t *allocd, + int32_t *used) +{ + if (!vector) { + return; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + if (allocd) { + *allocd += vector->size * elem_size + offset; + } + if (used) { + *used += vector->count * elem_size; + } +} + +ecs_vector_t* _ecs_vector_copy( + const ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset) +{ + if (!src) { + return NULL; + } + + ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size); + ecs_os_memcpy(dst, src, offset + elem_size * src->count); + return dst; +} + +/** The number of elements in a single chunk */ +#define CHUNK_COUNT (4096) + +/** Compute the chunk index from an id by stripping the first 12 bits */ +#define CHUNK(index) ((int32_t)index >> 12) + +/** This computes the offset of an index inside a chunk */ +#define OFFSET(index) ((int32_t)index & 0xFFF) + +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + +typedef struct chunk_t { + int32_t *sparse; /* Sparse array with indices to dense array */ + void *data; /* Store data in sparse array to reduce + * indirection and provide stable pointers. */ +} chunk_t; + +struct ecs_sparse_t { + ecs_vector_t *dense; /* Dense array with indices to sparse array. The + * dense array stores both alive and not alive + * sparse indices. The 'count' member keeps + * track of which indices are alive. */ + + ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ + ecs_size_t size; /* Element size */ + int32_t count; /* Number of alive entries */ + uint64_t max_id_local; /* Local max index (if no global is set) */ + uint64_t *max_id; /* Maximum issued sparse index */ +}; + +static +chunk_t* chunk_new( + ecs_sparse_t *sparse, + int32_t chunk_index) +{ + int32_t count = ecs_vector_count(sparse->chunks); + chunk_t *chunks; + + if (count <= chunk_index) { + ecs_vector_set_count(&sparse->chunks, chunk_t, chunk_index + 1); + chunks = ecs_vector_first(sparse->chunks, chunk_t); + ecs_os_memset(&chunks[count], 0, (1 + chunk_index - count) * ECS_SIZEOF(chunk_t)); + } else { + chunks = ecs_vector_first(sparse->chunks, chunk_t); + } + + ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); + + chunk_t *result = &chunks[chunk_index]; + ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); + + /* Initialize sparse array with zero's, as zero is used to indicate that the + * sparse element has not been paired with a dense element. Use zero + * as this means we can take advantage of calloc having a possibly better + * performance than malloc + memset. */ + result->sparse = ecs_os_calloc(ECS_SIZEOF(int32_t) * CHUNK_COUNT); + + /* Initialize the data array with zero's to guarantee that data is + * always initialized. When an entry is removed, data is reset back to + * zero. Initialize now, as this can take advantage of calloc. */ + result->data = ecs_os_calloc(sparse->size * CHUNK_COUNT); + + ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); + + return result; +} + +static +void chunk_free( + chunk_t *chunk) +{ + ecs_os_free(chunk->sparse); + ecs_os_free(chunk->data); +} + +static +chunk_t* get_chunk( + const ecs_sparse_t *sparse, + int32_t chunk_index) +{ + /* If chunk_index is below zero, application used an invalid entity id */ + ecs_assert(chunk_index >= 0, ECS_INVALID_PARAMETER, NULL); + chunk_t *result = ecs_vector_get(sparse->chunks, chunk_t, chunk_index); + if (result && !result->sparse) { + return NULL; + } + + return result; +} + +static +chunk_t* get_or_create_chunk( + ecs_sparse_t *sparse, + int32_t chunk_index) +{ + chunk_t *chunk = get_chunk(sparse, chunk_index); + if (chunk) { + return chunk; + } + + return chunk_new(sparse, chunk_index); +} + +static +void grow_dense( + ecs_sparse_t *sparse) +{ + ecs_vector_add(&sparse->dense, uint64_t); +} + +static +uint64_t strip_generation( + uint64_t *index_out) +{ + uint64_t index = *index_out; + uint64_t gen = index & ECS_GENERATION_MASK; + *index_out -= gen; + return gen; +} + +static +void assign_index( + chunk_t * chunk, + uint64_t * dense_array, + uint64_t index, + int32_t dense) +{ + /* Initialize sparse-dense pair. This assigns the dense index to the sparse + * array, and the sparse index to the dense array .*/ + chunk->sparse[OFFSET(index)] = dense; + dense_array[dense] = index; +} + +static +uint64_t inc_gen( + uint64_t index) +{ + /* When an index is deleted, its generation is increased so that we can do + * liveliness checking while recycling ids */ + return ECS_GENERATION_INC(index); +} + +static +uint64_t inc_id( + ecs_sparse_t *sparse) +{ + /* Generate a new id. The last issued id could be stored in an external + * variable, such as is the case with the last issued entity id, which is + * stored on the world. */ + return ++ (sparse->max_id[0]); +} + +static +uint64_t get_id( + const ecs_sparse_t *sparse) +{ + return sparse->max_id[0]; +} + +static +void set_id( + ecs_sparse_t *sparse, + uint64_t value) +{ + /* Sometimes the max id needs to be assigned directly, which typically + * happens when the API calls get_or_create for an id that hasn't been + * issued before. */ + sparse->max_id[0] = value; +} + +/* Pair dense id with new sparse id */ +static +uint64_t create_id( + ecs_sparse_t *sparse, + int32_t dense) +{ + uint64_t index = inc_id(sparse); + grow_dense(sparse); + + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + ecs_assert(chunk->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + assign_index(chunk, dense_array, index, dense); + + return index; +} + +/* Create new id */ +static +uint64_t new_index( + ecs_sparse_t *sparse) +{ + ecs_vector_t *dense = sparse->dense; + int32_t dense_count = ecs_vector_count(dense); + int32_t count = sparse->count ++; + + ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); + + if (count < dense_count) { + /* If there are unused elements in the dense array, return first */ + uint64_t *dense_array = ecs_vector_first(dense, uint64_t); + return dense_array[count]; + } else { + return create_id(sparse, count); + } +} + +/* Try obtaining a value from the sparse set, don't care about whether the + * provided index matches the current generation count. */ +static +void* try_sparse_any( + const ecs_sparse_t *sparse, + uint64_t index) +{ + strip_generation(&index); + + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); +} + +/* Try obtaining a value from the sparse set, make sure it's alive. */ +static +void* try_sparse( + const ecs_sparse_t *sparse, + uint64_t index) +{ + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + uint64_t gen = strip_generation(&index); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + + if (cur_gen != gen) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); +} + +/* Get value from sparse set when it is guaranteed that the value exists. This + * function is used when values are obtained using a dense index */ +static +void* get_sparse( + const ecs_sparse_t *sparse, + int32_t dense, + uint64_t index) +{ + strip_generation(&index); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; + + return DATA(chunk->data, sparse->size, offset); +} + +/* Swap dense elements. A swap occurs when an element is removed, or when a + * removed element is recycled. */ +static +void swap_dense( + ecs_sparse_t * sparse, + chunk_t * chunk_a, + int32_t a, + int32_t b) +{ + ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t index_a = dense_array[a]; + uint64_t index_b = dense_array[b]; + + chunk_t *chunk_b = get_or_create_chunk(sparse, CHUNK(index_b)); + assign_index(chunk_a, dense_array, index_a, b); + assign_index(chunk_b, dense_array, index_b, a); +} + +ecs_sparse_t* _ecs_sparse_new( + ecs_size_t size) +{ + ecs_sparse_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_sparse_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + result->size = size; + result->max_id_local = UINT64_MAX; + result->max_id = &result->max_id_local; + + /* Consume first value in dense array as 0 is used in the sparse array to + * indicate that a sparse element hasn't been paired yet. */ + ecs_vector_add(&result->dense, uint64_t); + result->count = 1; + + return result; +} + +void ecs_sparse_set_id_source( + ecs_sparse_t * sparse, + uint64_t * id_source) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + sparse->max_id = id_source; +} + +void ecs_sparse_clear( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_vector_each(sparse->chunks, chunk_t, chunk, { + chunk_free(chunk); + }); + + ecs_vector_free(sparse->chunks); + ecs_vector_set_count(&sparse->dense, uint64_t, 1); + + sparse->chunks = NULL; + sparse->count = 1; + sparse->max_id_local = 0; +} + +void ecs_sparse_free( + ecs_sparse_t *sparse) +{ + if (sparse) { + ecs_sparse_clear(sparse); + ecs_vector_free(sparse->dense); + ecs_os_free(sparse); + } +} + +uint64_t ecs_sparse_new_id( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return new_index(sparse); +} + +const uint64_t* ecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t new_count) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t dense_count = ecs_vector_count(sparse->dense); + int32_t count = sparse->count; + int32_t remaining = dense_count - count; + int32_t i, to_create = new_count - remaining; + + if (to_create > 0) { + ecs_sparse_set_size(sparse, dense_count + to_create); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + + for (i = 0; i < to_create; i ++) { + uint64_t index = create_id(sparse, count + i); + dense_array[dense_count + i] = index; + } + } + + sparse->count += new_count; + + return ecs_vector_get(sparse->dense, uint64_t, count); +} + +void* _ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t size) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + uint64_t index = new_index(sparse); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, size, OFFSET(index)); +} + +uint64_t ecs_sparse_last_id( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + return dense_array[sparse->count - 1]; +} + +void* _ecs_sparse_get_or_create( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; + + uint64_t gen = strip_generation(&index); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + /* Check if element is alive. If element is not alive, update indices so + * that the first unused dense element points to the sparse element. */ + int32_t count = sparse->count; + if (dense == count) { + /* If dense is the next unused element in the array, simply increase + * the count to make it part of the alive set. */ + sparse->count ++; + } else if (dense > count) { + /* If dense is not alive, swap it with the first unused element. */ + swap_dense(sparse, chunk, dense, count); + + /* First unused element is now last used element */ + sparse->count ++; + } else { + /* Dense is already alive, nothing to be done */ + } + } else { + /* Element is not paired yet. Must add a new element to dense array */ + grow_dense(sparse); + + ecs_vector_t *dense_vector = sparse->dense; + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + int32_t dense_count = ecs_vector_count(dense_vector) - 1; + int32_t count = sparse->count ++; + + /* If index is larger than max id, update max id */ + if (index >= get_id(sparse)) { + set_id(sparse, index + 1); + } + + if (count < dense_count) { + /* If there are unused elements in the list, move the first unused + * element to the end of the list */ + uint64_t unused = dense_array[count]; + chunk_t *unused_chunk = get_or_create_chunk(sparse, CHUNK(unused)); + assign_index(unused_chunk, dense_array, unused, dense_count); + } + + assign_index(chunk, dense_array, index, count); + dense_array[count] |= gen; + } + + return DATA(chunk->data, sparse->size, offset); +} + +void* _ecs_sparse_set( + ecs_sparse_t * sparse, + ecs_size_t elem_size, + uint64_t index, + void * value) +{ + void *ptr = _ecs_sparse_get_or_create(sparse, elem_size, index); + ecs_os_memcpy(ptr, value, elem_size); + return ptr; +} + +void* _ecs_sparse_remove_get( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + uint64_t gen = strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + if (gen != cur_gen) { + /* Generation doesn't match which means that the provided entity is + * already not alive. */ + return NULL; + } + + /* Increase generation */ + dense_array[dense] = index | inc_gen(cur_gen); + + int32_t count = sparse->count; + if (dense == (count - 1)) { + /* If dense is the last used element, simply decrease count */ + sparse->count --; + } else if (dense < count) { + /* If element is alive, move it to unused elements */ + swap_dense(sparse, chunk, dense, count - 1); + sparse->count --; + } else { + /* Element is not alive, nothing to be done */ + return NULL; + } + + /* Reset memory to zero on remove */ + return DATA(chunk->data, sparse->size, offset); + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return NULL; + } +} + +void ecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t index) +{ + void *ptr = _ecs_sparse_remove_get(sparse, 0, index); + if (ptr) { + ecs_os_memset(ptr, 0, sparse->size); + } +} + +void ecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + + uint64_t index_w_gen = index; + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + /* Increase generation */ + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + dense_array[dense] = index_w_gen; + } else { + /* Element is not paired and thus not alive, nothing to be done */ + } +} + +bool ecs_sparse_exists( + ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + return dense != 0; +} + +void* _ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + int32_t dense_index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); + (void)size; + + dense_index ++; + + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + return get_sparse(sparse, dense_index, dense_array[dense_index]); +} + +bool ecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index) +{ + return try_sparse(sparse, index) != NULL; +} + +void* _ecs_sparse_get_sparse( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse(sparse, index); +} + +void* _ecs_sparse_get_sparse_any( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse_any(sparse, index); +} + +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse) +{ + if (!sparse) { + return 0; + } + + return sparse->count - 1; +} + +int32_t ecs_sparse_size( + const ecs_sparse_t *sparse) +{ + if (!sparse) { + return 0; + } + + return ecs_vector_count(sparse->dense) - 1; +} + +const uint64_t* ecs_sparse_ids( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return &(ecs_vector_first(sparse->dense, uint64_t)[1]); +} + +void ecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); +} + +static +void sparse_copy( + ecs_sparse_t * dst, + const ecs_sparse_t * src) +{ + ecs_sparse_set_size(dst, ecs_sparse_size(src)); + const uint64_t *indices = ecs_sparse_ids(src); + + ecs_size_t size = src->size; + int32_t i, count = src->count; + + for (i = 0; i < count - 1; i ++) { + uint64_t index = indices[i]; + void *src_ptr = _ecs_sparse_get_sparse(src, size, index); + void *dst_ptr = _ecs_sparse_get_or_create(dst, size, index); + ecs_sparse_set_generation(dst, index); + ecs_os_memcpy(dst_ptr, src_ptr, size); + } + + set_id(dst, get_id(src)); + + ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); +} + +ecs_sparse_t* ecs_sparse_copy( + const ecs_sparse_t *src) +{ + if (!src) { + return NULL; + } + + ecs_sparse_t *dst = _ecs_sparse_new(src->size); + sparse_copy(dst, src); + + return dst; +} + +void ecs_sparse_restore( + ecs_sparse_t * dst, + const ecs_sparse_t * src) +{ + ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); + dst->count = 1; + if (src) { + sparse_copy(dst, src); + } +} + +void ecs_sparse_memory( + ecs_sparse_t *sparse, + int32_t *allocd, + int32_t *used) +{ + (void)sparse; + (void)allocd; + (void)used; +} + +#ifdef FLECS_READER_WRITER + + +static +void ecs_name_writer_alloc( + ecs_name_writer_t *writer, + int32_t len) +{ + writer->len = len; + if (writer->len > writer->max_len) { + ecs_os_free(writer->name); + writer->name = ecs_os_malloc(writer->len); + writer->max_len = writer->len; + } + writer->written = 0; +} + +static +bool ecs_name_writer_write( + ecs_name_writer_t *writer, + const char *buffer) +{ + ecs_size_t written = writer->len - writer->written; + char *name_ptr = ECS_OFFSET(writer->name, writer->written); + + ecs_assert(name_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buffer != NULL, ECS_INTERNAL_ERROR, NULL); + + if (written >= ECS_SIZEOF(int32_t)) { + ecs_os_memcpy(name_ptr, buffer, ECS_SIZEOF(int32_t)); + writer->written += ECS_SIZEOF(int32_t); + return writer->written != writer->len; + } else { + ecs_os_memcpy(name_ptr, buffer, written); + writer->written += written; + return false; + } +} + +static +void ecs_name_writer_reset( + ecs_name_writer_t *writer) +{ + writer->name = NULL; + writer->max_len = 0; + writer->len = 0; +} + +static +void ecs_table_writer_register_table( + ecs_writer_t *stream) +{ + ecs_world_t *world = stream->world; + ecs_table_writer_t *writer = &stream->table; + ecs_type_t type = ecs_type_find(world, writer->type_array, writer->type_count); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); + + writer->table = ecs_table_from_type(world, type); + ecs_assert(writer->table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_os_free(writer->type_array); + writer->type_array = NULL; + + ecs_data_t *data = ecs_table_get_or_create_data(writer->table); + if (data->entities) { + /* Remove any existing entities from entity index */ + ecs_vector_each(data->entities, ecs_entity_t, e_ptr, { + ecs_eis_delete(world, *e_ptr); + /* Don't increase generation to ensure the restored data exactly + * matches the data in the blob */ + ecs_eis_set_generation(world, *e_ptr); + }); + + return; + } else { + /* Set size of table to 0. This will initialize columns */ + ecs_table_set_size(world, writer->table, data, 0); + } + + ecs_assert(writer->table != NULL, ECS_INTERNAL_ERROR, NULL); +} + +static +void ecs_table_writer_finalize_table( + ecs_writer_t *stream) +{ + ecs_world_t *world = stream->world; + ecs_table_writer_t *writer = &stream->table; + + /* Register entities in table in entity index */ + ecs_data_t *data = ecs_table_get_data(writer->table); + ecs_vector_t *entity_vector = data->entities; + ecs_entity_t *entities = ecs_vector_first(entity_vector, ecs_entity_t); + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + int32_t i, count = ecs_vector_count(entity_vector); + + for (i = 0; i < count; i ++) { + ecs_record_t *record_ptr = ecs_eis_get_any(world, entities[i]); + + if (record_ptr) { + if (record_ptr->table != writer->table) { + ecs_table_t *table = record_ptr->table; + ecs_data_t *table_data = ecs_table_get_data(table); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_delete(world, + table, table_data, record_ptr->row - 1, false); + } + } else { + record_ptr = ecs_eis_get_or_create(world, entities[i]); + } + + record_ptr->row = i + 1; + record_ptr->table = writer->table; + + record_ptrs[i] = record_ptr; + + /* Strip entity from generation */ + ecs_entity_t id = entities[i] & ECS_ENTITY_MASK; + if (id >= world->stats.last_id) { + world->stats.last_id = id + 1; + } + if (id < ECS_HI_COMPONENT_ID) { + if (id >= world->stats.last_component_id) { + world->stats.last_component_id = id + 1; + } + } + } +} + +static +void ecs_table_writer_prepare_column( + ecs_writer_t *stream, + int32_t size) +{ + ecs_table_writer_t *writer = &stream->table; + ecs_data_t *data = ecs_table_get_or_create_data(writer->table); + + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (writer->column_index) { + ecs_column_t *column = &data->columns[writer->column_index - 1]; + + if (size) { + int32_t old_count = ecs_vector_count(column->data); + _ecs_vector_set_count(&column->data, ECS_VECTOR_U(size, 0), writer->row_count); + + /* Initialize new elements to 0 */ + void *buffer = ecs_vector_first_t(column->data, size, 0); + ecs_os_memset(ECS_OFFSET(buffer, old_count * size), 0, + (writer->row_count - old_count) * size); + } + + writer->column_vector = column->data; + writer->column_size = ecs_to_i16(size); + } else { + ecs_vector_set_count( + &data->entities, ecs_entity_t, writer->row_count); + + ecs_vector_set_count( + &data->record_ptrs, ecs_record_t*, writer->row_count); + + writer->column_vector = data->entities; + writer->column_size = ECS_SIZEOF(ecs_entity_t); + } + + writer->column_data = ecs_vector_first_t(writer->column_vector, + writer->column_size, + writer->column_alignment); + + writer->column_written = 0; +} + +static +void ecs_table_writer_next( + ecs_writer_t *stream) +{ + ecs_table_writer_t *writer = &stream->table; + + switch(writer->state) { + case EcsTableTypeSize: + writer->state = EcsTableType; + break; + + case EcsTableType: + writer->state = EcsTableSize; + break; + + case EcsTableSize: + writer->state = EcsTableColumn; + break; + + case EcsTableColumnHeader: + writer->state = EcsTableColumnSize; + break; + + case EcsTableColumnSize: + writer->state = EcsTableColumnData; + break; + + case EcsTableColumnNameHeader: + writer->state = EcsTableColumnNameLength; + break; + + case EcsTableColumnNameLength: + writer->state = EcsTableColumnName; + break; + + case EcsTableColumnName: + writer->row_index ++; + if (writer->row_index < writer->row_count) { + writer->state = EcsTableColumnNameLength; + break; + } + // fall through + + case EcsTableColumnData: + writer->column_index ++; + + if (writer->column_index > writer->table->column_count) { + ecs_table_writer_finalize_table(stream); + stream->state = EcsStreamHeader; + writer->column_written = 0; + writer->state = 0; + writer->column_index = 0; + writer->row_index = 0; + } else { + writer->state = EcsTableColumn; + } + break; + + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + break; + } +} + +static +ecs_size_t ecs_table_writer( + const char *buffer, + ecs_size_t size, + ecs_writer_t *stream) +{ + ecs_table_writer_t *writer = &stream->table; + ecs_size_t written = 0; + + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size >= ECS_SIZEOF(int32_t), ECS_INVALID_PARAMETER, NULL); + + if (!writer->state) { + writer->state = EcsTableTypeSize; + } + + switch(writer->state) { + case EcsTableTypeSize: + ecs_os_memcpy(&writer->type_count, buffer, ECS_SIZEOF(int32_t)); + writer->type_array = ecs_os_malloc(writer->type_count * ECS_SIZEOF(ecs_entity_t)); + writer->type_max_count = writer->type_count; + writer->type_written = 0; + written = ECS_SIZEOF(int32_t); + ecs_table_writer_next(stream); + break; + + case EcsTableType: + ecs_os_memcpy(ECS_OFFSET(writer->type_array, writer->type_written), buffer, ECS_SIZEOF(int32_t)); + written = ECS_SIZEOF(int32_t); + writer->type_written += written; + + if (writer->type_written == (writer->type_count * ECS_SIZEOF(ecs_entity_t))) { + ecs_table_writer_register_table(stream); + ecs_table_writer_next(stream); + } + break; + + case EcsTableSize: + ecs_os_memcpy(&writer->row_count, buffer, ECS_SIZEOF(int32_t)); + written += ECS_SIZEOF(int32_t); + ecs_table_writer_next(stream); + break; + + case EcsTableColumn: + ecs_os_memcpy(&writer->state, buffer, ECS_SIZEOF(ecs_blob_header_kind_t)); + if (writer->state != EcsTableColumnHeader && + writer->state != EcsTableColumnNameHeader) + { + stream->error = ECS_DESERIALIZE_FORMAT_ERROR; + goto error; + } + written += ECS_SIZEOF(int32_t); + break; + + case EcsTableColumnHeader: + case EcsTableColumnSize: { + int32_t column_size; + memcpy(&column_size, buffer, ECS_SIZEOF(int32_t)); + ecs_table_writer_prepare_column(stream, column_size); + ecs_table_writer_next(stream); + + written += ECS_SIZEOF(int32_t); + ecs_table_writer_next(stream); + + /* If column has no size, there will be no column data, so skip to the + * next state. */ + if (!writer->column_size) { + ecs_table_writer_next(stream); + } + break; + } + + case EcsTableColumnData: { + written = writer->row_count * writer->column_size - writer->column_written; + if (written > size) { + written = size; + } + + ecs_os_memcpy(ECS_OFFSET(writer->column_data, writer->column_written), buffer, written); + writer->column_written += written; + written = (((written - 1) / ECS_SIZEOF(int32_t)) + 1) * ECS_SIZEOF(int32_t); + + if (writer->column_written == writer->row_count * writer->column_size) { + ecs_table_writer_next(stream); + } + break; + } + + case EcsTableColumnNameHeader: + ecs_table_writer_prepare_column(stream, ECS_SIZEOF(EcsName)); + ecs_table_writer_next(stream); + // fall through + + case EcsTableColumnNameLength: { + int32_t name_size; + memcpy(&name_size, buffer, ECS_SIZEOF(int32_t)); + ecs_name_writer_alloc(&writer->name, name_size); + written = ECS_SIZEOF(int32_t); + ecs_table_writer_next(stream); + break; + } + + case EcsTableColumnName: { + written = ECS_SIZEOF(int32_t); + if (!ecs_name_writer_write(&writer->name, buffer)) { + EcsName *name_ptr = &((EcsName*)writer->column_data)[writer->row_index]; + name_ptr->value = writer->name.name; + + if (name_ptr->alloc_value) { + ecs_os_free(name_ptr->alloc_value); + } + + name_ptr->alloc_value = writer->name.name; + + /* Don't overwrite entity name */ + ecs_name_writer_reset(&writer->name); + + ecs_table_writer_next(stream); + } + break; + } + + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + break; + } + + ecs_assert(written <= size, ECS_INTERNAL_ERROR, NULL); + + return written; +error: + return -1; +} + +int ecs_writer_write( + const char *buffer, + int32_t size, + ecs_writer_t *writer) +{ + int32_t written = 0, total_written = 0, remaining = size; + + if (!size) { + return 0; + } + + ecs_assert(size >= ECS_SIZEOF(int32_t), ECS_INVALID_PARAMETER, NULL); + ecs_assert(size % 4 == 0, ECS_INVALID_PARAMETER, NULL); + + while (total_written < size) { + if (writer->state == EcsStreamHeader) { + writer->state = *(ecs_blob_header_kind_t*)ECS_OFFSET(buffer, + total_written); + + if (writer->state != EcsTableHeader) { + writer->error = ECS_DESERIALIZE_FORMAT_ERROR; + goto error; + } + + written = ECS_SIZEOF(ecs_blob_header_kind_t); + } else + if (writer->state == EcsTableHeader) { + written = ecs_table_writer(ECS_OFFSET(buffer, total_written), + remaining, writer); + } + + if (!written) { + break; + } + + if (written == (ecs_size_t)-1) { + goto error; + } + + remaining -= written; + total_written += written; + } + + ecs_assert(total_written <= size, ECS_INTERNAL_ERROR, NULL); + + return total_written == 0; +error: + return -1; +} + +ecs_writer_t ecs_writer_init( + ecs_world_t *world) +{ + return (ecs_writer_t){ + .world = world, + .state = EcsStreamHeader, + }; +} + +#endif + +#ifdef FLECS_MODULE + + +char* ecs_module_path_from_c( + const char *c_name) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + const char *ptr; + char ch; + + for (ptr = c_name; (ch = *ptr); ptr++) { + if (isupper(ch)) { + ch = ecs_to_i8(tolower(ch)); + if (ptr != c_name) { + ecs_strbuf_appendstrn(&str, ".", 1); + } + } + + ecs_strbuf_appendstrn(&str, &ch, 1); + } + + return ecs_strbuf_get(&str); +} + +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t init_action, + const char *module_name, + void *handles_out, + size_t handles_size) +{ + ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->name_prefix; + + char *path = ecs_module_path_from_c(module_name); + ecs_entity_t e = ecs_lookup_fullpath(world, path); + ecs_os_free(path); + + if (!e) { + ecs_trace_1("import %s", module_name); + ecs_log_push(); + + /* Load module */ + init_action(world); + + /* Lookup module entity (must be registered by module) */ + e = ecs_lookup_fullpath(world, module_name); + ecs_assert(e != 0, ECS_MODULE_UNDEFINED, module_name); + + ecs_log_pop(); + } + + /* Copy value of module component in handles_out parameter */ + if (handles_size && handles_out) { + void *handles_ptr = ecs_get_mut_w_entity(world, e, e, NULL); + ecs_os_memcpy(handles_out, handles_ptr, ecs_from_size_t(handles_size)); + } + + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->name_prefix = old_name_prefix; + + return e; +} + +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name) +{ + ecs_assert(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + + char *import_func = (char*)module_name; /* safe */ + char *module = (char*)module_name; + + if (!ecs_os_has_modules() || !ecs_os_has_dl()) { + ecs_os_err( + "library loading not supported, set module_to_dl, dlopen, dlclose " + "and dlproc os API callbacks first"); + return 0; + } + + /* If no module name is specified, try default naming convention for loading + * the main module from the library */ + if (!import_func) { + import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); + ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); + + const char *ptr; + char ch, *bptr = import_func; + bool capitalize = true; + for (ptr = library_name; (ch = *ptr); ptr ++) { + if (ch == '.') { + capitalize = true; + } else { + if (capitalize) { + *bptr = ecs_to_i8(toupper(ch)); + bptr ++; + capitalize = false; + } else { + *bptr = ecs_to_i8(tolower(ch)); + bptr ++; + } + } + } + + *bptr = '\0'; + + module = ecs_os_strdup(import_func); + ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); + + ecs_os_strcat(bptr, "Import"); + } + + char *library_filename = ecs_os_module_to_dl(library_name); + if (!library_filename) { + ecs_os_err("failed to find library file for '%s'", library_name); + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace_1("found file '%s' for library '%s'", + library_filename, library_name); + } + + ecs_os_dl_t dl = ecs_os_dlopen(library_filename); + if (!dl) { + ecs_os_err("failed to load library '%s' ('%s')", + library_name, library_filename); + + ecs_os_free(library_filename); + + if (module != module_name) { + ecs_os_free(module); + } + + return 0; + } else { + ecs_trace_1("library '%s' ('%s') loaded", + library_name, library_filename); + } + + ecs_module_action_t action = (ecs_module_action_t) + ecs_os_dlproc(dl, import_func); + if (!action) { + ecs_os_err("failed to load import function %s from library %s", + import_func, library_name); + ecs_os_free(library_filename); + ecs_os_dlclose(dl); + return 0; + } else { + ecs_trace_1("found import function '%s' in library '%s' for module '%s'", + import_func, library_name, module); + } + + /* Do not free id, as it will be stored as the component identifier */ + ecs_entity_t result = ecs_import(world, action, module, NULL, 0); + + if (import_func != module_name) { + ecs_os_free(import_func); + } + + if (module != module_name) { + ecs_os_free(module); + } + + ecs_os_free(library_filename); + + return result; +} + +ecs_entity_t ecs_new_module( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + size_t size, + size_t alignment) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + assert(world->magic == ECS_WORLD_MAGIC); + + if (!e) { + char *module_path = ecs_module_path_from_c(name); + e = ecs_new_from_fullpath(world, module_path); + + EcsName *name_ptr = ecs_get_mut(world, e, EcsName, NULL); + name_ptr->symbol = name; + + ecs_os_free(module_path); + } + + ecs_entity_t result = ecs_new_component(world, e, NULL, size, alignment); + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + + /* Add module tag */ + ecs_add_entity(world, result, EcsModule); + + /* Add module to itself. This way we have all the module information stored + * in a single contained entity that we can use for namespacing */ + ecs_set_ptr_w_entity(world, result, result, size, NULL); + + /* Set the current scope to the module */ + ecs_set_scope(world, result); + + return result; +} + +#endif + +#ifdef FLECS_QUEUE + +struct ecs_queue_t { + ecs_vector_t *data; + int32_t index; +}; + +ecs_queue_t* _ecs_queue_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->data = _ecs_vector_new(elem_size, offset, elem_count); + result->index = 0; + return result; +} + +ecs_queue_t* _ecs_queue_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) +{ + ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->data = _ecs_vector_from_array(elem_size, offset, elem_count, array); + result->index = 0; + return result; +} + +void* _ecs_queue_push( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset) +{ + int32_t size = ecs_vector_size(buffer->data); + int32_t count = ecs_vector_count(buffer->data); + void *result; + + if (count == buffer->index) { + result = _ecs_vector_add(&buffer->data, elem_size, offset); + } else { + result = _ecs_vector_get(buffer->data, elem_size, offset, buffer->index); + } + + buffer->index = (buffer->index + 1) % size; + + return result; +} + +void ecs_queue_free( + ecs_queue_t *buffer) +{ + ecs_vector_free(buffer->data); + ecs_os_free(buffer); +} + +void* _ecs_queue_get( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + int32_t count = ecs_vector_count(buffer->data); + int32_t size = ecs_vector_size(buffer->data); + index = ((buffer->index - count + size) + (int32_t)index) % size; + return _ecs_vector_get(buffer->data, elem_size, offset, index); +} + +void* _ecs_queue_last( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset) +{ + int32_t index = buffer->index; + if (!index) { + index = ecs_vector_size(buffer->data); + } + + return _ecs_vector_get(buffer->data, elem_size, offset, index - 1); +} + +int32_t ecs_queue_index( + ecs_queue_t *buffer) +{ + return buffer->index; +} + +int32_t ecs_queue_count( + ecs_queue_t *buffer) +{ + return ecs_vector_count(buffer->data); +} + +#endif + + +#ifdef FLECS_STATS + +#ifndef FLECS_SYSTEM_PRIVATE_H +#define FLECS_SYSTEM_PRIVATE_H + + +typedef struct EcsSystem { + ecs_iter_action_t action; /* Callback to be invoked for matching it */ + void *ctx; /* Userdata for system */ + + ecs_entity_t entity; /* Entity id of system, used for ordering */ + ecs_query_t *query; /* System query */ + ecs_on_demand_out_t *on_demand; /* Keep track of [out] column refs */ + ecs_system_status_action_t status_action; /* Status action */ + void *status_ctx; /* User data for status action */ + ecs_entity_t tick_source; /* Tick source associated with system */ + + int32_t invoke_count; /* Number of times system is invoked */ + FLECS_FLOAT time_spent; /* Time spent on running system */ + FLECS_FLOAT time_passed; /* Time passed since last invocation */ +} EcsSystem; + +/* Invoked when system becomes active / inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const EcsSystem *system_data); + +/* Internal function to run a system */ +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param, + bool ran_by_app); + +#endif +#ifndef FLECS_PIPELINE_PRIVATE_H +#define FLECS_PIPELINE_PRIVATE_H + + +/** Instruction data for pipeline. + * This type is the element type in the "ops" vector of a pipeline and contains + * information about the set of systems that need to be ran before a merge. */ +typedef struct ecs_pipeline_op_t { + int32_t count; /**< Number of systems to run before merge */ +} ecs_pipeline_op_t; + +typedef struct EcsPipelineQuery { + ecs_query_t *query; + ecs_query_t *build_query; + int32_t match_count; + ecs_vector_t *ops; +} EcsPipelineQuery; + +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// + +int32_t ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline); + +int32_t ecs_pipeline_begin( + ecs_world_t *world, + ecs_entity_t pipeline); + +void ecs_pipeline_end( + ecs_world_t *world); + +void ecs_pipeline_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time); + + +//////////////////////////////////////////////////////////////////////////////// +//// Worker API +//////////////////////////////////////////////////////////////////////////////// + +void ecs_worker_begin( + ecs_world_t *world); + +bool ecs_worker_sync( + ecs_world_t *world); + +void ecs_worker_end( + ecs_world_t *world); + +void ecs_workers_progress( + ecs_world_t *world); + +#endif + +static +int32_t t_next( + int32_t t) +{ + return (t + 1) % ECS_STAT_WINDOW; +} + +static +int32_t t_prev( + int32_t t) +{ + return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; +} + +static +void _record_gauge( + ecs_gauge_t *m, + int32_t t, + float value) +{ + m->avg[t] = value; + m->min[t] = value; + m->max[t] = value; +} + +static +float _record_counter( + ecs_counter_t *m, + int32_t t, + float value) +{ + int32_t tp = t_prev(t); + float prev = m->value[tp]; + m->value[t] = value; + _record_gauge((ecs_gauge_t*)m, t, value - prev); + return value - prev; +} + +/* Macro's to silence conversion warnings without adding casts everywhere */ +#define record_gauge(m, t, value)\ + _record_gauge(m, t, (float)value) + +#define record_counter(m, t, value)\ + _record_counter(m, t, (float)value) + +static +void print_value( + const char *name, + float value) +{ + ecs_size_t len = ecs_os_strlen(name); + printf("%s: %*s %.2f\n", name, 32 - len, "", value); +} + +static +void print_gauge( + const char *name, + int32_t t, + const ecs_gauge_t *m) +{ + print_value(name, m->avg[t]); +} + +static +void print_counter( + const char *name, + int32_t t, + const ecs_counter_t *m) +{ + print_value(name, m->rate.avg[t]); +} + +void ecs_gauge_reduce( + ecs_gauge_t *dst, + int32_t t_dst, + ecs_gauge_t *src, + int32_t t_src) +{ + bool min_set = false; + dst->min[t_dst] = 0; + dst->avg[t_dst] = 0; + dst->max[t_dst] = 0; + + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; + if (!min_set || (src->min[t] < dst->min[t_dst])) { + dst->min[t_dst] = src->min[t]; + min_set = true; + } + if ((src->max[t] > dst->max[t_dst])) { + dst->max[t_dst] = src->max[t]; + } + } +} + +void ecs_get_world_stats( + ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t t = s->t = t_next(s->t); + + float delta_world_time = record_counter(&s->world_time_total_raw, t, world->stats.world_time_total_raw); + record_counter(&s->world_time_total, t, world->stats.world_time_total); + record_counter(&s->frame_time_total, t, world->stats.frame_time_total); + record_counter(&s->system_time_total, t, world->stats.system_time_total); + record_counter(&s->merge_time_total, t, world->stats.merge_time_total); + + float delta_frame_count = record_counter(&s->frame_count_total, t, world->stats.frame_count_total); + record_counter(&s->merge_count_total, t, world->stats.merge_count_total); + record_counter(&s->pipeline_build_count_total, t, world->stats.pipeline_build_count_total); + record_counter(&s->systems_ran_frame, t, world->stats.systems_ran_frame); + + record_gauge(&s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); + + record_gauge(&s->entity_count, t, ecs_sparse_count(world->store.entity_index)); + record_gauge(&s->component_count, t, ecs_count_entity(world, ecs_typeid(EcsComponent))); + record_gauge(&s->query_count, t, ecs_vector_count(world->queries)); + record_gauge(&s->system_count, t, ecs_count_entity(world, ecs_typeid(EcsSystem))); + + record_counter(&s->new_count, t, world->new_count); + record_counter(&s->bulk_new_count, t, world->bulk_new_count); + record_counter(&s->delete_count, t, world->delete_count); + record_counter(&s->clear_count, t, world->clear_count); + record_counter(&s->add_count, t, world->add_count); + record_counter(&s->remove_count, t, world->remove_count); + record_counter(&s->set_count, t, world->set_count); + record_counter(&s->discard_count, t, world->discard_count); + + /* Compute table statistics */ + int32_t empty_table_count = 0; + int32_t singleton_table_count = 0; + int32_t matched_table_count = 0, matched_entity_count = 0; + + int32_t i, count = ecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + int32_t entity_count = ecs_table_count(table); + + if (!entity_count) { + empty_table_count ++; + } + + /* Singleton tables are tables that have just one entity that also has + * itself in the table type. */ + if (entity_count == 1) { + ecs_data_t *data = ecs_table_get_data(table); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + if (ecs_type_has_entity(world, table->type, entities[0])) { + singleton_table_count ++; + } + } + + /* If this table matches with queries and is not empty, increase the + * matched table & matched entity count. These statistics can be used to + * compute actual fragmentation ratio for queries. */ + int32_t queries_matched = ecs_vector_count(table->queries); + if (queries_matched && entity_count) { + matched_table_count ++; + matched_entity_count += entity_count; + } + } + + record_gauge(&s->matched_table_count, t, matched_table_count); + record_gauge(&s->matched_entity_count, t, matched_entity_count); + + record_gauge(&s->table_count, t, count); + record_gauge(&s->empty_table_count, t, empty_table_count); + record_gauge(&s->singleton_table_count, t, singleton_table_count); +} + +void ecs_get_query_stats( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_stats_t *s) +{ + (void)world; + + int32_t t = s->t = t_next(s->t); + + int32_t i, entity_count = 0, count = ecs_vector_count(query->tables); + ecs_matched_table_t *matched_tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + for (i = 0; i < count; i ++) { + ecs_matched_table_t *matched = &matched_tables[i]; + if (matched->iter_data.table) { + entity_count += ecs_table_count(matched->iter_data.table); + } + } + + record_gauge(&s->matched_table_count, t, count); + record_gauge(&s->matched_empty_table_count, t, + ecs_vector_count(query->empty_tables)); + record_gauge(&s->matched_entity_count, t, entity_count); +} + +bool ecs_get_system_stats( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) +{ + const EcsSystem *ptr = ecs_get(world, system, EcsSystem); + if (!ptr) { + return false; + } + + ecs_get_query_stats(world, ptr->query, &s->query_stats); + int32_t t = s->query_stats.t; + + record_counter(&s->time_spent, t, ptr->time_spent); + record_counter(&s->invoke_count, t, ptr->invoke_count); + record_gauge(&s->active, t, !ecs_has_entity(world, system, EcsInactive)); + record_gauge(&s->enabled, t, !ecs_has_entity(world, system, EcsDisabled)); + + return true; +} + +static ecs_system_stats_t* get_system_stats( + ecs_map_t *systems, + ecs_entity_t system) +{ + ecs_system_stats_t *s = ecs_map_get(systems, ecs_system_stats_t, system); + if (!s) { + ecs_system_stats_t stats; + memset(&stats, 0, sizeof(ecs_system_stats_t)); + ecs_map_set(systems, system, &stats); + s = ecs_map_get(systems, ecs_system_stats_t, system); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); + } + + return s; +} + +bool ecs_get_pipeline_stats( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) +{ + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + if (!pq) { + return false; + } + + /* First find out how many systems are matched by the pipeline */ + ecs_iter_t it = ecs_query_iter(pq->query); + int32_t count = 0; + while (ecs_query_next(&it)) { + count += it.count; + } + + if (!s->system_stats) { + s->system_stats = ecs_map_new(ecs_system_stats_t, count); + } + + /* Also count synchronization points */ + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + count += ecs_vector_count(ops); + + /* Make sure vector is large enough to store all systems & sync points */ + ecs_vector_set_count(&s->systems, ecs_entity_t, count - 1); + ecs_entity_t *systems = ecs_vector_first(s->systems, ecs_entity_t); + + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(pq->query); + int32_t i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + systems[i_system ++] = it.entities[i]; + ran_since_merge ++; + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + systems[i_system ++] = 0; /* 0 indicates a merge point */ + } + + ecs_system_stats_t *sys_stats = get_system_stats( + s->system_stats, it.entities[i]); + ecs_get_system_stats(world, it.entities[i], sys_stats); + } + } + + ecs_assert(i_system == (count - 1), ECS_INTERNAL_ERROR, NULL); + + return true; +} + +void ecs_dump_world_stats( + ecs_world_t *world, + const ecs_world_stats_t *s) +{ + int32_t t = s->t; + + print_counter("Frame", t, &s->frame_count_total); + printf("-------------------------------------\n"); + print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); + print_counter("systems ran last frame", t, &s->systems_ran_frame); + printf("\n"); + print_value("target FPS", world->stats.target_fps); + print_value("time scale", world->stats.time_scale); + printf("\n"); + print_gauge("actual FPS", t, &s->fps); + print_counter("frame time", t, &s->frame_time_total); + print_counter("system time", t, &s->system_time_total); + print_counter("merge time", t, &s->merge_time_total); + print_counter("simulation time elapsed", t, &s->world_time_total); + printf("\n"); + print_gauge("entity count", t, &s->entity_count); + print_gauge("component count", t, &s->component_count); + print_gauge("query count", t, &s->query_count); + print_gauge("system count", t, &s->system_count); + print_gauge("table count", t, &s->table_count); + print_gauge("singleton table count", t, &s->singleton_table_count); + print_gauge("empty table count", t, &s->empty_table_count); + printf("\n"); + print_counter("deferred new operations", t, &s->new_count); + print_counter("deferred bulk_new operations", t, &s->bulk_new_count); + print_counter("deferred delete operations", t, &s->delete_count); + print_counter("deferred clear operations", t, &s->clear_count); + print_counter("deferred add operations", t, &s->add_count); + print_counter("deferred remove operations", t, &s->remove_count); + print_counter("deferred set operations", t, &s->set_count); + print_counter("discarded operations", t, &s->discard_count); + printf("\n"); +} + +#endif + +#ifdef FLECS_SNAPSHOT + + +/* World snapshot */ +struct ecs_snapshot_t { + ecs_world_t *world; + ecs_sparse_t *entity_index; + ecs_vector_t *tables; + ecs_entity_t last_id; + ecs_filter_t filter; +}; + +static +ecs_data_t* duplicate_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *main_data) +{ + ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); + + int32_t i, column_count = table->column_count; + ecs_entity_t *components = ecs_vector_first(table->type, ecs_entity_t); + + result->columns = ecs_os_memdup( + main_data->columns, ECS_SIZEOF(ecs_column_t) * column_count); + + /* Copy entities */ + result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t); + ecs_entity_t *entities = ecs_vector_first(result->entities, ecs_entity_t); + + /* Copy record ptrs */ + result->record_ptrs = ecs_vector_copy(main_data->record_ptrs, ecs_record_t*); + + /* Copy each column */ + for (i = 0; i < column_count; i ++) { + ecs_entity_t component = components[i]; + ecs_column_t *column = &result->columns[i]; + + if (component > ECS_HI_COMPONENT_ID) { + column->data = NULL; + continue; + } + + ecs_c_info_t *cdata = ecs_get_c_info(world, component); + int16_t size = column->size; + int16_t alignment = column->alignment; + ecs_copy_t copy; + + if (cdata && (copy = cdata->lifecycle.copy)) { + int32_t count = ecs_vector_count(column->data); + ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, count); + ecs_vector_set_count_t(&dst_vec, size, alignment, count); + void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment); + void *ctx = cdata->lifecycle.ctx; + + ecs_xtor_t ctor = cdata->lifecycle.ctor; + if (ctor) { + ctor(world, component, entities, dst_ptr, ecs_to_size_t(size), + count, ctx); + } + + void *src_ptr = ecs_vector_first_t(column->data, size, alignment); + copy(world, component, entities, entities, dst_ptr, src_ptr, + ecs_to_size_t(size), count, ctx); + + column->data = dst_vec; + } else { + column->data = ecs_vector_copy_t(column->data, size, alignment); + } + } + + return result; +} + +static +ecs_snapshot_t* snapshot_create( + ecs_world_t *world, + const ecs_sparse_t *entity_index, + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_snapshot_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_snapshot_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->world = world; + + /* If no iterator is provided, the snapshot will be taken of the entire + * world, and we can simply copy the entity index as it will be restored + * entirely upon snapshote restore. */ + if (!iter && entity_index) { + result->entity_index = ecs_sparse_copy(entity_index); + result->tables = ecs_vector_new(ecs_table_leaf_t, 0); + } + + ecs_iter_t iter_stack; + if (!iter) { + iter_stack = ecs_filter_iter(world, NULL); + iter = &iter_stack; + next = ecs_filter_next; + } + + /* If an iterator is provided, this is a filterred snapshot. In this case we + * have to patch the entity index one by one upon restore, as we don't want + * to affect entities that were not part of the snapshot. */ + else { + result->entity_index = NULL; + } + + /* Iterate tables in iterator */ + while (next(iter)) { + ecs_table_t *t = iter->table->table; + + if (t->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_data_t *data = ecs_table_get_data(t); + if (!data || !data->entities || !ecs_vector_count(data->entities)) { + continue; + } + + ecs_table_leaf_t *l = ecs_vector_add(&result->tables, ecs_table_leaf_t); + + l->table = t; + l->type = t->type; + l->data = duplicate_data(world, t, data); + } + + return result; +} + +/** Create a snapshot */ +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *world) +{ + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + NULL, + NULL); + + result->last_id = world->stats.last_id; + + return result; +} + +/** Create a filtered snapshot */ +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_world_t *world = iter->world; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + iter, + next); + + result->last_id = world->stats.last_id; + + return result; +} + +/** Restore a snapshot */ +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot) +{ + bool is_filtered = true; + + if (snapshot->entity_index) { + ecs_sparse_restore(world->store.entity_index, snapshot->entity_index); + ecs_sparse_free(snapshot->entity_index); + is_filtered = false; + } + + if (!is_filtered) { + world->stats.last_id = snapshot->last_id; + } + + ecs_table_leaf_t *leafs = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); + int32_t l = 0, count = ecs_vector_count(snapshot->tables); + int32_t t, table_count = ecs_sparse_count(world->store.tables); + + for (t = 0; t < table_count; t ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, t); + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_table_leaf_t *leaf = NULL; + if (l < count) { + leaf = &leafs[l]; + } + + if (leaf && leaf->table == table) { + /* If the snapshot is filtered, update the entity index for the + * entities in the snapshot. If the snapshot was not filtered + * the entity index would have been replaced entirely, and this + * is not necessary. */ + if (is_filtered) { + ecs_vector_each(leaf->data->entities, ecs_entity_t, e_ptr, { + ecs_record_t *r = ecs_eis_get(world, *e_ptr); + if (r && r->table) { + ecs_data_t *data = ecs_table_get_data(r->table); + + /* Data must be not NULL, otherwise entity index could + * not point to it */ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + bool is_monitored; + int32_t row = ecs_record_to_row(r->row, &is_monitored); + + /* Always delete entity, so that even if the entity is + * in the current table, there won't be duplicates */ + ecs_table_delete(world, r->table, data, row, false); + } else { + ecs_eis_set_generation(world, *e_ptr); + } + }); + + int32_t old_count = ecs_table_count(table); + int32_t new_count = ecs_table_data_count(leaf->data); + + ecs_data_t *data = ecs_table_get_data(table); + data = ecs_table_merge(world, table, table, data, leaf->data); + + /* Run OnSet systems for merged entities */ + ecs_entities_t components = ecs_type_to_entities(table->type); + ecs_run_set_systems(world, &components, table, data, + old_count, new_count, true); + + ecs_os_free(leaf->data->columns); + } else { + ecs_table_replace_data(world, table, leaf->data); + } + + ecs_os_free(leaf->data); + l ++; + } else { + /* If the snapshot is not filtered, the snapshot should restore the + * world to the exact state it was in. When a snapshot is filtered, + * it should only update the entities that were in the snapshot. + * If a table is found that was not in the snapshot, and the + * snapshot was not filtered, clear the table. */ + if (!is_filtered) { + /* Use clear_silent so no triggers are fired */ + ecs_table_clear_silent(world, table); + } + } + + table->alloc_count ++; + } + + /* If snapshot was not filtered, run OnSet systems now. This cannot be done + * while restoring the snapshot, because the world is in an inconsistent + * state while restoring. When a snapshot is filtered, the world is not left + * in an inconsistent state, which makes running OnSet systems while + * restoring safe */ + if (!is_filtered) { + for (t = 0; t < table_count; t ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, t); + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_entities_t components = ecs_type_to_entities(table->type); + ecs_data_t *table_data = ecs_table_get_data(table); + int32_t entity_count = ecs_table_data_count(table_data); + + ecs_run_set_systems(world, &components, table, + table_data, 0, entity_count, true); + } + } + + ecs_vector_free(snapshot->tables); + + ecs_os_free(snapshot); +} + +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot, + const ecs_filter_t *filter) +{ + ecs_snapshot_iter_t iter = { + .filter = filter ? *filter : (ecs_filter_t){0}, + .tables = snapshot->tables, + .index = 0 + }; + + return (ecs_iter_t){ + .world = snapshot->world, + .table_count = ecs_vector_count(snapshot->tables), + .iter.snapshot = iter + }; +} + +bool ecs_snapshot_next( + ecs_iter_t *it) +{ + ecs_snapshot_iter_t *iter = &it->iter.snapshot; + ecs_table_leaf_t *tables = ecs_vector_first(iter->tables, ecs_table_leaf_t); + int32_t count = ecs_vector_count(iter->tables); + int32_t i; + + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = tables[i].table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = tables[i].data; + + /* Table must have data or it wouldn't have been added */ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_table_match_filter(it->world, table, &iter->filter)) { + continue; + } + + iter->table.table = table; + it->table = &iter->table; + it->table_columns = data->columns; + it->count = ecs_table_data_count(data); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + iter->index = i + 1; + + return true; + } + + return false; +} + +/** Cleanup snapshot */ +void ecs_snapshot_free( + ecs_snapshot_t *snapshot) +{ + ecs_sparse_free(snapshot->entity_index); + + ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); + int32_t i, count = ecs_vector_count(snapshot->tables); + for (i = 0; i < count; i ++) { + ecs_table_leaf_t *leaf = &tables[i]; + ecs_table_clear_data(leaf->table, leaf->data); + ecs_os_free(leaf->data); + } + + ecs_vector_free(snapshot->tables); + ecs_os_free(snapshot); +} + +#endif + +#ifdef FLECS_DBG + + +ecs_table_t *ecs_dbg_find_table( + ecs_world_t *world, + ecs_type_t type) +{ + ecs_table_t *table = ecs_table_from_type(world, type); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table; +} + +void ecs_dbg_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_dbg_table_t *dbg_out) +{ + ecs_assert(dbg_out != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + + *dbg_out = (ecs_dbg_table_t){.table = table}; + + dbg_out->type = table->type; + dbg_out->systems_matched = table->queries; + + /* Determine components from parent/base entities */ + ecs_entity_t *entities = ecs_vector_first(table->type, ecs_entity_t); + int32_t i, count = ecs_vector_count(table->type); + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + if (ECS_HAS_ROLE(e, CHILDOF)) { + ecs_dbg_entity_t parent_dbg; + ecs_dbg_entity(world, e & ECS_COMPONENT_MASK, &parent_dbg); + + ecs_dbg_table_t parent_table_dbg; + ecs_dbg_table(world, parent_dbg.table, &parent_table_dbg); + + /* Owned and shared components are available from container */ + dbg_out->container = ecs_type_merge( + world, dbg_out->container, parent_dbg.type, NULL); + dbg_out->container = ecs_type_merge( + world, dbg_out->container, parent_table_dbg.shared, NULL); + + /* Add entity to list of parent entities */ + dbg_out->parent_entities = ecs_type_add( + world, dbg_out->parent_entities, e & ECS_COMPONENT_MASK); + } + + if (ECS_HAS_ROLE(e, INSTANCEOF)) { + ecs_dbg_entity_t base_dbg; + ecs_dbg_entity(world, e & ECS_COMPONENT_MASK, &base_dbg); + + ecs_dbg_table_t base_table_dbg; + ecs_dbg_table(world, base_dbg.table, &base_table_dbg); + + /* Owned and shared components are available from base */ + dbg_out->shared = ecs_type_merge( + world, dbg_out->shared, base_dbg.type, NULL); + dbg_out->shared = ecs_type_merge( + world, dbg_out->shared, base_table_dbg.shared, NULL); + + /* Never inherit EcsName or EcsPrefab */ + dbg_out->shared = ecs_type_merge( + world, dbg_out->shared, NULL, ecs_type(EcsName)); + dbg_out->shared = ecs_type_merge( + world, dbg_out->shared, NULL, ecs_type_from_entity(world, EcsPrefab)); + + /* Shared components are always masked by owned components */ + dbg_out->shared = ecs_type_merge( + world, dbg_out->shared, NULL, table->type); + + /* Add entity to list of base entities */ + dbg_out->base_entities = ecs_type_add( + world, dbg_out->base_entities, e & ECS_COMPONENT_MASK); + + /* Add base entities of entity to list of base entities */ + dbg_out->base_entities = ecs_type_add( + world, base_table_dbg.base_entities, e & ECS_COMPONENT_MASK); + } + } + + ecs_data_t *data = ecs_table_get_data(table); + if (data) { + dbg_out->entities = ecs_vector_first(data->entities, ecs_entity_t); + dbg_out->entities_count = ecs_vector_count(data->entities); + } +} + +ecs_table_t* ecs_dbg_get_table( + ecs_world_t *world, + int32_t index) +{ + if (ecs_sparse_count(world->store.tables) <= index) { + return NULL; + } + + return ecs_sparse_get( + world->store.tables, ecs_table_t, index); +} + +bool ecs_dbg_filter_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_filter_t *filter) +{ + return ecs_table_match_filter(world, table, filter); +} + +void ecs_dbg_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_dbg_entity_t *dbg_out) +{ + *dbg_out = (ecs_dbg_entity_t){.entity = entity}; + + ecs_entity_info_t info = { 0 }; + if (ecs_get_info(world, entity, &info)) { + dbg_out->table = info.table; + dbg_out->row = info.row; + dbg_out->is_watched = info.is_watched; + dbg_out->type = info.table ? info.table->type : NULL; + } +} + +#endif + +#ifdef FLECS_READER_WRITER + + +static +bool iter_table( + ecs_table_reader_t *reader, + ecs_iter_t *it, + ecs_iter_next_action_t next, + bool skip_builtin) +{ + bool table_found = false; + + while (next(it)) { + ecs_table_t *table = it->table->table; + + reader->table = table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = ecs_table_get_data(table); + reader->data = data; + reader->table_index ++; + + if (skip_builtin && reader->table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!data || !it->count) { + continue; + } + + table_found = true; + break; + } + + return table_found; +} + +static +void next_table( + ecs_reader_t *stream, + ecs_table_reader_t *reader) +{ + int32_t count; + + /* First iterate all component tables, as component data must always be + * stored in a blob before anything else */ + bool table_found = iter_table( + reader, &stream->component_iter, stream->component_next, false); + + /* If all components have been added, add the regular data tables. Make sure + * to not add component tables again, in case the provided iterator also + * matches component tables. */ + if (!table_found) { + table_found = iter_table( + reader, &stream->data_iter, stream->data_next, true); + count = stream->data_iter.count; + } else { + count = stream->component_iter.count; + } + + if (!table_found) { + stream->state = EcsFooterSegment; + } else { + reader->type = reader->table->type; + reader->total_columns = reader->table->column_count + 1; + reader->column_index = 0; + reader->row_count = count; + } +} + +static +void ecs_table_reader_next( + ecs_reader_t *stream) +{ + ecs_table_reader_t *reader = &stream->table; + + switch(reader->state) { + case EcsTableHeader: + reader->state = EcsTableTypeSize; + break; + case EcsTableTypeSize: + reader->state = EcsTableType; + reader->type_written = 0; + break; + + case EcsTableType: + reader->state = EcsTableSize; + break; + + case EcsTableSize: { + reader->state = EcsTableColumnHeader; + break; + } + + case EcsTableColumnHeader: + reader->state = EcsTableColumnSize; + if (!reader->column_index) { + reader->column_vector = reader->data->entities; + reader->column_size = ECS_SIZEOF(ecs_entity_t); + } else { + ecs_column_t *column = + &reader->data->columns[reader->column_index - 1]; + reader->column_vector = column->data; + reader->column_size = column->size; + reader->column_alignment = column->alignment; + } + break; + + case EcsTableColumnSize: + reader->state = EcsTableColumnData; + reader->column_data = ecs_vector_first_t(reader->column_vector, + reader->column_size, reader->column_alignment); + reader->column_written = 0; + break; + + case EcsTableColumnNameHeader: { + reader->state = EcsTableColumnNameLength; + ecs_column_t *column = + &reader->data->columns[reader->column_index - 1]; + reader->column_vector = column->data; + reader->column_data = ecs_vector_first(reader->column_vector, EcsName); + reader->row_index = 0; + break; + } + + case EcsTableColumnNameLength: + reader->state = EcsTableColumnName; + reader->row_index ++; + break; + + case EcsTableColumnName: + if (reader->row_index < reader->row_count) { + reader->state = EcsTableColumnNameLength; + break; + } + // fall through + + case EcsTableColumnData: + reader->column_index ++; + if (reader->column_index == reader->total_columns) { + reader->state = EcsTableHeader; + next_table(stream, reader); + } else { + ecs_entity_t *type_buffer = ecs_vector_first(reader->type, ecs_entity_t); + if (reader->column_index >= 1) { + ecs_entity_t e = type_buffer[reader->column_index - 1]; + + if (e != ecs_typeid(EcsName)) { + reader->state = EcsTableColumnHeader; + } else { + reader->state = EcsTableColumnNameHeader; + } + } else { + reader->state = EcsTableColumnHeader; + } + } + break; + + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + return; +} + +static +ecs_size_t ecs_table_reader( + char *buffer, + ecs_size_t size, + ecs_reader_t *stream) +{ + if (!size) { + return 0; + } + + if (size < ECS_SIZEOF(int32_t)) { + return -1; + } + + ecs_table_reader_t *reader = &stream->table; + ecs_size_t read = 0; + + if (!reader->state) { + next_table(stream, reader); + reader->state = EcsTableHeader; + } + + switch(reader->state) { + case EcsTableHeader: + ecs_os_memcpy(buffer, &(ecs_blob_header_kind_t){EcsTableHeader}, ECS_SIZEOF(EcsTableHeader)); + read = ECS_SIZEOF(ecs_blob_header_kind_t); + ecs_table_reader_next(stream); + break; + + case EcsTableTypeSize: + ecs_os_memcpy(buffer, &(int32_t){ecs_vector_count(reader->type)}, ECS_SIZEOF(int32_t)); + read = ECS_SIZEOF(int32_t); + ecs_table_reader_next(stream); + break; + + case EcsTableType: { + ecs_entity_t *type_array = ecs_vector_first(reader->type, ecs_entity_t); + ecs_os_memcpy(buffer, ECS_OFFSET(type_array, reader->type_written), ECS_SIZEOF(int32_t)); + reader->type_written += ECS_SIZEOF(int32_t); + read = ECS_SIZEOF(int32_t); + + if (reader->type_written == ecs_vector_count(reader->type) * ECS_SIZEOF(ecs_entity_t)) { + ecs_table_reader_next(stream); + } + break; + } + + case EcsTableSize: + ecs_os_memcpy(buffer, &(int32_t){ecs_table_count(reader->table)}, ECS_SIZEOF(int32_t)); + read = ECS_SIZEOF(int32_t); + ecs_table_reader_next(stream); + break; + + case EcsTableColumnHeader: + ecs_os_memcpy(buffer, &(ecs_blob_header_kind_t){EcsTableColumnHeader}, ECS_SIZEOF(ecs_blob_header_kind_t)); + read = ECS_SIZEOF(ecs_blob_header_kind_t); + ecs_table_reader_next(stream); + break; + + case EcsTableColumnSize: { + int32_t column_size = reader->column_size; + ecs_os_memcpy(buffer, &column_size, ECS_SIZEOF(int32_t)); + read = ECS_SIZEOF(ecs_blob_header_kind_t); + ecs_table_reader_next(stream); + + if (!reader->column_size) { + ecs_table_reader_next(stream); + } + break; + } + + case EcsTableColumnData: { + ecs_size_t column_bytes = reader->column_size * reader->row_count; + read = column_bytes - reader->column_written; + if (read > size) { + read = size; + } + + ecs_os_memcpy(buffer, ECS_OFFSET(reader->column_data, reader->column_written), read); + reader->column_written += read; + ecs_assert(reader->column_written <= column_bytes, ECS_INTERNAL_ERROR, NULL); + + ecs_size_t align = (((read - 1) / ECS_SIZEOF(int32_t)) + 1) * ECS_SIZEOF(int32_t); + if (align != read) { + /* Initialize padding bytes to 0 to keep valgrind happy */ + ecs_os_memset(ECS_OFFSET(buffer, read), 0, align - read); + + /* Set read to align so that data is always aligned to 4 bytes */ + read = align; + + /* Buffer sizes are expected to be aligned to 4 bytes and the rest + * of the serialized data is aligned to 4 bytes. Should never happen + * that adding padding bytes exceeds the size. */ + ecs_assert(read <= size, ECS_INTERNAL_ERROR, NULL); + } + + if (reader->column_written == column_bytes) { + ecs_table_reader_next(stream); + } + break; + } + + case EcsTableColumnNameHeader: + ecs_os_memcpy(buffer, &(ecs_blob_header_kind_t){EcsTableColumnNameHeader}, ECS_SIZEOF(ecs_blob_header_kind_t)); + read = ECS_SIZEOF(ecs_blob_header_kind_t); + ecs_table_reader_next(stream); + break; + + case EcsTableColumnNameLength: { + reader->name = ((EcsName*)reader->column_data)[reader->row_index].value; + reader->name_len = ecs_os_strlen(reader->name) + 1; + reader->name_written = 0; + int32_t name_len = reader->name_len; + ecs_os_memcpy(buffer, &name_len, ECS_SIZEOF(int32_t)); + // *(int32_t*)buffer = reader->name_len; + read = ECS_SIZEOF(int32_t); + ecs_table_reader_next(stream); + break; + } + + case EcsTableColumnName: + read = reader->name_len - reader->name_written; + if (read >= ECS_SIZEOF(int32_t)) { + + int32_t i; + for (i = 0; i < 4; i ++) { + *(char*)ECS_OFFSET(buffer, i) = + *(char*)ECS_OFFSET(reader->name, reader->name_written + i); + } + + reader->name_written += ECS_SIZEOF(int32_t); + } else { + ecs_os_memcpy(buffer, ECS_OFFSET(reader->name, reader->name_written), read); + ecs_os_memset(ECS_OFFSET(buffer, read), 0, ECS_SIZEOF(int32_t) - read); + reader->name_written += read; + } + + /* Always align buffer to multiples of 4 bytes */ + read = ECS_SIZEOF(int32_t); + + if (reader->name_written == reader->name_len) { + ecs_table_reader_next(stream); + } + + break; + + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + ecs_assert(read % 4 == 0, ECS_INTERNAL_ERROR, NULL); + + return read; +} + +int32_t ecs_reader_read( + char *buffer, + int32_t size, + ecs_reader_t *reader) +{ + int32_t read, total_read = 0, remaining = size; + + if (!size) { + return 0; + } + + ecs_assert(size >= ECS_SIZEOF(int32_t), ECS_INVALID_PARAMETER, NULL); + ecs_assert(size % 4 == 0, ECS_INVALID_PARAMETER, NULL); + + if (reader->state == EcsTableSegment) { + while ((read = ecs_table_reader(ECS_OFFSET(buffer, total_read), remaining, reader))) { + remaining -= read; + total_read += read; + + if (reader->state != EcsTableSegment) { + break; + } + + ecs_assert(remaining % 4 == 0, ECS_INTERNAL_ERROR, NULL); + } + } + + return total_read; +} + +ecs_reader_t ecs_reader_init( + ecs_world_t *world) +{ + ecs_reader_t result = { + .world = world, + .state = EcsTableSegment, + .component_iter = ecs_filter_iter(world, &(ecs_filter_t){ + .include = ecs_type(EcsComponent) + }), + .component_next = ecs_filter_next, + .data_iter = ecs_filter_iter(world, NULL), + .data_next = ecs_filter_next + }; + + return result; +} + +ecs_reader_t ecs_reader_init_w_iter( + ecs_iter_t *it, + ecs_iter_next_action_t next) +{ + ecs_world_t *world = it->world; + + ecs_reader_t result = { + .world = world, + .state = EcsTableSegment, + .component_iter = ecs_filter_iter(world, &(ecs_filter_t){ + .include = ecs_type(EcsComponent) + }), + .component_next = ecs_filter_next, + .data_iter = *it, + .data_next = next + }; + + return result; +} + +#endif + +#ifdef FLECS_BULK + + +static +void bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter, + bool is_delete) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + int32_t i, count = ecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!ecs_table_match_filter(world, table, filter)) { + continue; + } + + /* Remove entities from index */ + ecs_data_t *data = ecs_table_get_data(table); + if (!data) { + /* If table has no data, there's nothing to delete */ + continue; + } + + ecs_vector_t *entities = NULL; + if (data) { + entities = data->entities; + } + + ecs_vector_each(entities, ecs_entity_t, e_ptr, { + ecs_eis_delete(world, *e_ptr); + }) + + /* Both filters passed, clear table */ + if (is_delete) { + ecs_table_clear(world, table); + } else { + ecs_table_clear_silent(world, table); + } + } +} + +static +void merge_table( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table, + ecs_entities_t *to_add, + ecs_entities_t *to_remove) +{ + if (!dst_table->type) { + /* If this removes all components, clear table */ + ecs_table_clear(world, src_table); + } else { + /* Merge table into dst_table */ + if (dst_table != src_table) { + ecs_data_t *src_data = ecs_table_get_data(src_table); + int32_t dst_count = ecs_table_count(dst_table); + int32_t src_count = ecs_table_count(src_table); + + if (to_remove && to_remove->count && src_data) { + ecs_run_remove_actions(world, src_table, + src_data, 0, src_count, to_remove, false); + } + + ecs_data_t *dst_data = ecs_table_get_data(dst_table); + dst_data = ecs_table_merge( + world, dst_table, src_table, dst_data, src_data); + + if (to_add && to_add->count && dst_data) { + ecs_run_add_actions(world, dst_table, dst_data, + dst_count, src_count, to_add, false, true); + } + } + } +} + +/* -- Public API -- */ + +void ecs_bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + bulk_delete(world, filter, true); +} + +void ecs_bulk_add_remove_type( + ecs_world_t *world, + ecs_type_t to_add, + ecs_type_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_entities_t to_add_array = ecs_type_to_entities(to_add); + ecs_entities_t to_remove_array = ecs_type_to_entities(to_remove); + + ecs_entities_t added = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count), + .count = 0 + }; + + ecs_entities_t removed = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count), + .count = 0 + }; + + int32_t i, count = ecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!ecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = ecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + dst_table = ecs_table_traverse_add( + world, dst_table, &to_add_array, &added); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (table == dst_table || (!added.count && !removed.count)) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + merge_table(world, dst_table, table, &added, &removed); + added.count = 0; + removed.count = 0; + } +} + +void ecs_bulk_add_type( + ecs_world_t *world, + ecs_type_t to_add, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_add != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_entities_t to_add_array = ecs_type_to_entities(to_add); + ecs_entities_t added = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count), + .count = 0 + }; + + int32_t i, count = ecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!ecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = ecs_table_traverse_add( + world, table, &to_add_array, &added); + + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!added.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, &added, NULL); + added.count = 0; + } +} + +void ecs_bulk_add_entity( + ecs_world_t *world, + ecs_entity_t to_add, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_add != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_entities_t to_add_array = { .array = &to_add, .count = 1 }; + + ecs_entity_t added_entity; + ecs_entities_t added = { + .array = &added_entity, + .count = 0 + }; + + int32_t i, count = ecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!ecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = ecs_table_traverse_add( + world, table, &to_add_array, &added); + + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!added.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, &added, NULL); + added.count = 0; + } +} + +void ecs_bulk_remove_type( + ecs_world_t *world, + ecs_type_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_entities_t to_remove_array = ecs_type_to_entities(to_remove); + ecs_entities_t removed = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count), + .count = 0 + }; + + int32_t i, count = ecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!ecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = ecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!removed.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, NULL, &removed); + removed.count = 0; + } +} + +void ecs_bulk_remove_entity( + ecs_world_t *world, + ecs_entity_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_entities_t to_remove_array = { .array = &to_remove, .count = 1 }; + + ecs_entity_t removed_entity; + ecs_entities_t removed = { + .array = &removed_entity, + .count = 0 + }; + + int32_t i, count = ecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!ecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = ecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!removed.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, NULL, &removed); + removed.count = 0; + } +} + +#endif + +#ifdef FLECS_DIRECT_ACCESS + + +/* Prefix with "da" so that they don't conflict with other get_column's */ + +static +ecs_column_t *da_get_column( + ecs_table_t *table, + int32_t column) +{ + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(column <= table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_data_t *data = table->data; + if (data && data->columns) { + return &table->data->columns[column]; + } else { + return NULL; + } +} + +static +ecs_column_t *da_get_or_create_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column) +{ + ecs_column_t *c = da_get_column(table, column); + if (!c && (!table->data || !table->data->columns)) { + ecs_data_t *data = ecs_table_get_or_create_data(table); + ecs_init_data(world, table, data); + c = da_get_column(table, column); + } + ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); + return c; +} + +static +ecs_entity_t* get_entity_array( + ecs_table_t *table, + int32_t row) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data->entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *array = ecs_vector_first(table->data->entities, ecs_entity_t); + return &array[row]; +} + +/* -- Public API -- */ + +ecs_type_t ecs_table_get_type( + ecs_table_t *table) +{ + return table->type; +} + +ecs_record_t* ecs_record_find( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_record_t *r = ecs_eis_get(world, entity); + if (r) { + return r; + } else { + return NULL; + } +} + +ecs_record_t* ecs_record_ensure( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_record_t *r = ecs_eis_get_or_create(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + return r; +} + +ecs_record_t ecs_table_insert( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record) +{ + ecs_data_t *data = ecs_table_get_or_create_data(table); + int32_t index = ecs_table_append(world, table, data, entity, record, true); + if (record) { + record->table = table; + record->row = index + 1; + } + return (ecs_record_t){table, index + 1}; +} + +int32_t ecs_table_find_column( + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_type_index_of(table->type, component); +} + +ecs_vector_t* ecs_table_get_column( + ecs_table_t *table, + int32_t column) +{ + ecs_column_t *c = da_get_column(table, column); + return c ? c->data : NULL; +} + +ecs_vector_t* ecs_table_set_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t* vector) +{ + ecs_column_t *c = da_get_or_create_column(world, table, column); + if (vector) { + ecs_vector_assert_size(vector, c->size); + } else { + ecs_vector_t *entities = ecs_table_get_entities(table); + if (entities) { + int32_t count = ecs_vector_count(entities); + vector = ecs_table_get_column(table, column); + if (!vector) { + vector = ecs_vector_new_t(c->size, c->alignment, count); + } else { + ecs_vector_set_count_t(&vector, c->size, c->alignment, count); + } + } + } + c->data = vector; + + return vector; +} + +ecs_vector_t* ecs_table_get_entities( + ecs_table_t *table) +{ + ecs_data_t *data = table->data; + if (!data) { + return NULL; + } + + return data->entities; +} + +ecs_vector_t* ecs_table_get_records( + ecs_table_t *table) +{ + ecs_data_t *data = table->data; + if (!data) { + return NULL; + } + + return data->record_ptrs; +} + +void ecs_table_set_entities( + ecs_table_t *table, + ecs_vector_t *entities, + ecs_vector_t *records) +{ + ecs_vector_assert_size(entities, sizeof(ecs_entity_t)); + ecs_vector_assert_size(records, sizeof(ecs_record_t*)); + ecs_assert(ecs_vector_count(entities) == ecs_vector_count(records), + ECS_INVALID_PARAMETER, NULL); + + ecs_data_t *data = table->data; + if (!data) { + data = ecs_table_get_or_create_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + } + + data->entities = entities; + data->record_ptrs = records; +} + +void ecs_records_clear( + ecs_vector_t *records) +{ + int32_t i, count = ecs_vector_count(records); + ecs_record_t **r = ecs_vector_first(records, ecs_record_t*); + + for (i = 0; i < count; i ++) { + r[i]->table = NULL; + if (r[i]->row < 0) { + r[i]->row = -1; + } else { + r[i]->row = 0; + } + } +} + +void ecs_records_update( + ecs_world_t *world, + ecs_vector_t *entities, + ecs_vector_t *records, + ecs_table_t *table) +{ + int32_t i, count = ecs_vector_count(records); + ecs_entity_t *e = ecs_vector_first(entities, ecs_entity_t); + ecs_record_t **r = ecs_vector_first(records, ecs_record_t*); + + for (i = 0; i < count; i ++) { + r[i] = ecs_record_ensure(world, e[i]); + ecs_assert(r[i] != NULL, ECS_INTERNAL_ERROR, NULL); + + r[i]->table = table; + r[i]->row = i + 1; + } +} + +void ecs_table_delete_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector) +{ + if (!vector) { + vector = ecs_table_get_column(table, column); + if (!vector) { + return; + } + + ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data->columns != NULL, ECS_INTERNAL_ERROR, NULL); + + table->data->columns[column].data = NULL; + } + + ecs_column_t *c = da_get_or_create_column(world, table, column); + ecs_vector_assert_size(vector, c->size); + + ecs_c_info_t *c_info = table->c_info[column]; + ecs_xtor_t dtor; + if (c_info && (dtor = c_info->lifecycle.dtor)) { + ecs_entity_t *entities = get_entity_array(table, 0); + int16_t alignment = c->alignment; + int32_t count = ecs_vector_count(vector); + void *ptr = ecs_vector_first_t(vector, c->size, alignment); + dtor(world, c_info->component, entities, ptr, ecs_to_size_t(c->size), + count, c_info->lifecycle.ctx); + } + + if (c->data == vector) { + c->data = NULL; + } + + ecs_vector_free(vector); +} + +void* ecs_record_get_column( + ecs_record_t *r, + int32_t column, + size_t c_size) +{ + (void)c_size; + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_column(table, column); + if (!c) { + return NULL; + } + + int16_t size = c->size; + ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + void *array = ecs_vector_first_t(c->data, c->size, c->alignment); + bool is_watched; + int32_t row = ecs_record_to_row(r->row, &is_watched); + return ECS_OFFSET(array, size * row); +} + +void ecs_record_copy_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + const void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = ecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_c_info_t *c_info = table->c_info[column]; + ecs_copy_t copy; + if (c_info && (copy = c_info->lifecycle.copy)) { + ecs_entity_t *entities = get_entity_array(table, row); + copy(world, c_info->component, entities, entities, ptr, value, c_size, + count, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, value, size * count); + } +} + +void ecs_record_copy_pod_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + const void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + (void)c_size; + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = ecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_os_memcpy(ptr, value, size * count); +} + +void ecs_record_move_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!ecs_from_size_t(c_size) || ecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = ecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_c_info_t *c_info = table->c_info[column]; + ecs_move_t move; + if (c_info && (move = c_info->lifecycle.move)) { + ecs_entity_t *entities = get_entity_array(table, row); + move(world, c_info->component, entities, entities, ptr, value, c_size, + count, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, value, size * count); + } +} + +#endif + +/* -- Private functions -- */ + +ecs_stage_t *ecs_get_stage( + ecs_world_t **world_ptr) +{ + ecs_world_t *world = *world_ptr; + + ecs_assert(world->magic == ECS_WORLD_MAGIC || + world->magic == ECS_THREAD_MAGIC, + ECS_INTERNAL_ERROR, + NULL); + + if (world->magic == ECS_WORLD_MAGIC) { + if (world->in_progress) { + return &world->temp_stage; + } else { + return &world->stage; + } + } else if (world->magic == ECS_THREAD_MAGIC) { + ecs_thread_t *thread = (ecs_thread_t*)world; + *world_ptr = thread->world; + return thread->stage; + } + + return NULL; +} + +/* Evaluate component monitor. If a monitored entity changed it will have set a + * flag in one of the world's component monitors. Queries can register + * themselves with component monitors to determine whether they need to rematch + * with tables. */ +static +void eval_component_monitor( + ecs_world_t *world, + ecs_component_monitor_t *mon) +{ + if (!mon->rematch) { + return; + } + + ecs_vector_t *eval[ECS_HI_COMPONENT_ID]; + int32_t eval_count = 0; + + int32_t i; + for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) { + if (mon->dirty_flags[i]) { + eval[eval_count ++] = mon->monitors[i]; + mon->dirty_flags[i] = 0; + } + } + + for (i = 0; i < eval_count; i ++) { + ecs_vector_each(eval[i], ecs_query_t*, q_ptr, { + ecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { + .kind = EcsQueryTableRematch + }); + }); + } + + mon->rematch = false; +} + +void ecs_component_monitor_mark( + ecs_component_monitor_t *mon, + ecs_entity_t component) +{ + /* Only flag if there are actually monitors registered, so that we + * don't waste cycles evaluating monitors if there's no interest */ + if (mon->monitors[component]) { + mon->dirty_flags[component] = true; + mon->rematch = true; + } +} + +void ecs_component_monitor_register( + ecs_component_monitor_t *mon, + ecs_entity_t component, + ecs_query_t *query) +{ + ecs_assert(mon != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Ignore component ids > ECS_HI_COMPONENT_ID */ + if(component >= ECS_HI_COMPONENT_ID) { + return; + } + + ecs_query_t **q = ecs_vector_add(&mon->monitors[component], ecs_query_t*); + *q = query; +} + +static +void ecs_component_monitor_free( + ecs_component_monitor_t *mon) +{ + int i; + for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) { + ecs_vector_free(mon->monitors[i]); + } +} + +static +void init_store(ecs_world_t *world) { + ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); + + /* Initialize entity index */ + world->store.entity_index = ecs_sparse_new(ecs_record_t); + ecs_sparse_set_id_source(world->store.entity_index, &world->stats.last_id); + + /* Initialize root table */ + world->store.tables = ecs_sparse_new(ecs_table_t); + + /* Initialize table map */ + world->store.table_map = ecs_map_new(ecs_vector_t*, 8); + + /* Initialize one root table per stage */ + ecs_init_root_table(world); +} + +static +void clean_tables( + ecs_world_t *world) +{ + int32_t i, count = ecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *t = ecs_sparse_get(world->store.tables, ecs_table_t, i); + ecs_table_free(world, t); + } + + /* Clear the root table */ + if (count) { + ecs_table_reset(world, &world->store.root); + } +} + +static +void fini_store(ecs_world_t *world) { + clean_tables(world); + ecs_sparse_free(world->store.tables); + ecs_table_free(world, &world->store.root); + ecs_sparse_free(world->store.entity_index); + + ecs_map_iter_t it = ecs_map_iter(world->store.table_map); + ecs_vector_t *tables; + while ((tables = ecs_map_next_ptr(&it, ecs_vector_t*, NULL))) { + ecs_vector_free(tables); + } + + ecs_map_free(world->store.table_map); +} + +/* -- Public functions -- */ + +ecs_world_t *ecs_mini(void) { + ecs_os_init(); + + ecs_trace_1("bootstrap"); + ecs_log_push(); + + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); + } + + if (!ecs_os_has_threading()) { + ecs_trace_1("threading not available"); + } + + if (!ecs_os_has_time()) { + ecs_trace_1("time management not available"); + } + + ecs_world_t *world = ecs_os_calloc(sizeof(ecs_world_t)); + ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); + + world->magic = ECS_WORLD_MAGIC; + memset(&world->c_info, 0, sizeof(ecs_c_info_t) * ECS_HI_COMPONENT_ID); + + world->t_info = ecs_map_new(ecs_c_info_t, 0); + world->fini_actions = NULL; + + world->aliases = NULL; + + world->queries = ecs_vector_new(ecs_query_t*, 0); + world->fini_tasks = ecs_vector_new(ecs_entity_t, 0); + world->child_tables = NULL; + world->name_prefix = NULL; + + memset(&world->component_monitors, 0, sizeof(world->component_monitors)); + memset(&world->parent_monitors, 0, sizeof(world->parent_monitors)); + + world->type_handles = ecs_map_new(ecs_entity_t, 0); + world->on_activate_components = ecs_map_new(ecs_on_demand_in_t, 0); + world->on_enable_components = ecs_map_new(ecs_on_demand_in_t, 0); + + world->stage_count = 2; + world->worker_stages = NULL; + world->workers = NULL; + world->workers_waiting = 0; + world->workers_running = 0; + world->quit_workers = false; + world->in_progress = false; + world->is_merging = false; + world->auto_merge = true; + world->measure_frame_time = false; + world->measure_system_time = false; + world->should_quit = false; + world->locking_enabled = false; + world->pipeline = 0; + + world->frame_start_time = (ecs_time_t){0, 0}; + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); + } + + world->stats.target_fps = 0; + world->stats.last_id = 0; + + world->stats.delta_time_raw = 0; + world->stats.delta_time = 0; + world->stats.time_scale = 1.0; + world->stats.frame_time_total = 0; + world->stats.sleep_err = 0; + world->stats.system_time_total = 0; + world->stats.merge_time_total = 0; + world->stats.world_time_total = 0; + world->stats.frame_count_total = 0; + world->stats.merge_count_total = 0; + world->stats.systems_ran_frame = 0; + world->stats.pipeline_build_count_total = 0; + + world->range_check_enabled = true; + + world->fps_sleep = 0; + + world->context = NULL; + + world->arg_fps = 0; + world->arg_threads = 0; + + ecs_stage_init(world, &world->stage); + ecs_stage_init(world, &world->temp_stage); + init_store(world); + + world->stage.world = world; + world->temp_stage.world = world; + + ecs_bootstrap(world); + + ecs_log_pop(); + + return world; +} + +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); + +#ifdef FLECS_MODULE_H + ecs_trace_1("import builtin modules"); + ecs_log_push(); +#ifdef FLECS_SYSTEM_H + ECS_IMPORT(world, FlecsSystem); +#endif +#ifdef FLECS_PIPELINE_H + ECS_IMPORT(world, FlecsPipeline); +#endif +#ifdef FLECS_TIMER_H + ECS_IMPORT(world, FlecsTimer); +#endif + ecs_log_pop(); +#endif + + return world; +} + +#define ARG(short, long, action)\ + if (i < argc) {\ + if (argv[i][0] == '-') {\ + if (argv[i][1] == '-') {\ + if (long && !strcmp(&argv[i][2], long ? long : "")) {\ + action;\ + parsed = true;\ + }\ + } else {\ + if (short && argv[i][1] == short) {\ + action;\ + parsed = true;\ + }\ + }\ + }\ + } + +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) +{ + (void)argc; + (void)argv; + return ecs_init(); +} + +static +void on_demand_in_map_deinit( + ecs_map_t *map) +{ + ecs_map_iter_t it = ecs_map_iter(map); + ecs_on_demand_in_t *elem; + + while ((elem = ecs_map_next(&it, ecs_on_demand_in_t, NULL))) { + ecs_vector_free(elem->systems); + } + + ecs_map_free(map); +} + +static +void ctor_init_zero( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)entity_ptr; + (void)ctx; + ecs_os_memset(ptr, 0, ecs_from_size_t(size) * count); +} + +void ecs_notify_tables( + ecs_world_t *world, + ecs_table_event_t *event) +{ + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = ecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i); + ecs_table_notify(world, table, event); + } +} + +void ecs_set_component_actions_w_entity( + ecs_world_t *world, + ecs_entity_t component, + EcsComponentLifecycle *lifecycle) +{ +#ifndef NDEBUG + const EcsComponent *component_ptr = ecs_get(world, component, EcsComponent); + + /* Cannot register lifecycle actions for things that aren't a component */ + ecs_assert(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Cannot register lifecycle actions for components with size 0 */ + ecs_assert(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); +#endif + + ecs_c_info_t *c_info = ecs_get_or_create_c_info(world, component); + ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL); + + if (c_info->lifecycle_set) { + ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL); + ecs_assert(c_info->lifecycle.ctor == lifecycle->ctor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.dtor == lifecycle->dtor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.copy == lifecycle->copy, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.move == lifecycle->move, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + } else { + c_info->component = component; + c_info->lifecycle = *lifecycle; + c_info->lifecycle_set = true; + + /* If no constructor is set, invoking any of the other lifecycle actions + * is not safe as they will potentially access uninitialized memory. For + * ease of use, if no constructor is specified, set a default one that + * initializes the component to 0. */ + if (!lifecycle->ctor && (lifecycle->dtor || lifecycle->copy || lifecycle->move)) { + c_info->lifecycle.ctor = ctor_init_zero; + } + + ecs_notify_tables(world, &(ecs_table_event_t) { + .kind = EcsTableComponentInfo, + .component = component + }); + } +} + +bool ecs_component_has_actions( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_c_info_t *c_info = ecs_get_c_info(world, component); + return (c_info != NULL) && c_info->lifecycle_set; +} + +void ecs_atfini( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_assert(action != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_action_elem_t *elem = ecs_vector_add(&world->fini_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +} + +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_assert(action != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stage = ecs_get_stage(&world); + + ecs_action_elem_t *elem = ecs_vector_add(&stage->post_frame_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +} + +/* Unset data in tables */ +static +void fini_unset_tables( + ecs_world_t *world) +{ + int32_t i, count = ecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(world->store.tables, ecs_table_t, i); + ecs_table_unset(world, table); + } +} + +/* Invoke fini actions */ +static +void fini_actions( + ecs_world_t *world) +{ + ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { + elem->action(world, elem->ctx); + }); + + ecs_vector_free(world->fini_actions); +} + +/* Cleanup component lifecycle callbacks & systems */ +static +void fini_component_lifecycle( + ecs_world_t *world) +{ + int32_t i; + for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) { + ecs_vector_free(world->c_info[i].on_add); + ecs_vector_free(world->c_info[i].on_remove); + } + + ecs_map_iter_t it = ecs_map_iter(world->t_info); + ecs_c_info_t *c_info; + while ((c_info = ecs_map_next(&it, ecs_c_info_t, NULL))) { + ecs_vector_free(c_info->on_add); + ecs_vector_free(c_info->on_remove); + } + + ecs_map_free(world->t_info); +} + +/* Cleanup queries */ +static +void fini_queries( + ecs_world_t *world) +{ + /* Set world->queries to NULL, so ecs_query_free won't attempt to remove + * itself from the vector */ + ecs_vector_t *query_vec = world->queries; + world->queries = NULL; + + int32_t i, count = ecs_vector_count(query_vec); + ecs_query_t **queries = ecs_vector_first(query_vec, ecs_query_t*); + for (i = 0; i < count; i ++) { + ecs_query_free(queries[i]); + } + + ecs_vector_free(query_vec); +} + +/* Cleanup stages */ +static +void fini_stages( + ecs_world_t *world) +{ + ecs_stage_deinit(world, &world->stage); + ecs_stage_deinit(world, &world->temp_stage); +} + +/* Cleanup child table admin */ +static +void fini_child_tables( + ecs_world_t *world) +{ + ecs_map_iter_t it = ecs_map_iter(world->child_tables); + ecs_vector_t *tables; + while ((tables = ecs_map_next_ptr(&it, ecs_vector_t*, NULL))) { + ecs_vector_free(tables); + } + + ecs_map_free(world->child_tables); +} + +/* Cleanup aliases */ +static +void fini_aliases( + ecs_world_t *world) +{ + int32_t i, count = ecs_vector_count(world->aliases); + ecs_alias_t *aliases = ecs_vector_first(world->aliases, ecs_alias_t); + + for (i = 0; i < count; i ++) { + ecs_os_free(aliases[i].name); + } + + ecs_vector_free(world->aliases); +} + +/* Cleanup misc structures */ +static +void fini_misc( + ecs_world_t *world) +{ + on_demand_in_map_deinit(world->on_activate_components); + on_demand_in_map_deinit(world->on_enable_components); + ecs_map_free(world->type_handles); + ecs_vector_free(world->fini_tasks); + ecs_component_monitor_free(&world->component_monitors); + ecs_component_monitor_free(&world->parent_monitors); +} + +/* The destroyer of worlds */ +int ecs_fini( + ecs_world_t *world) +{ + assert(world->magic == ECS_WORLD_MAGIC); + assert(!world->in_progress); + assert(!world->is_merging); + + fini_unset_tables(world); + + fini_actions(world); + + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); + } + + fini_stages(world); + + fini_store(world); + + fini_component_lifecycle(world); + + fini_queries(world); + + fini_child_tables(world); + + fini_aliases(world); + + fini_misc(world); + + /* In case the application tries to use the memory of the freed world, this + * will trigger an assert */ + world->magic = 0; + + ecs_increase_timer_resolution(0); + + /* End of the world */ + ecs_os_free(world); + + ecs_os_fini(); + + return 0; +} + +void ecs_dim( + ecs_world_t *world, + int32_t entity_count) +{ + assert(world->magic == ECS_WORLD_MAGIC); + ecs_eis_set_size(world, entity_count + ECS_HI_COMPONENT_ID); +} + +void ecs_dim_type( + ecs_world_t *world, + ecs_type_t type, + int32_t entity_count) +{ + assert(world->magic == ECS_WORLD_MAGIC); + if (type) { + ecs_table_t *table = ecs_table_from_type(world, type); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = ecs_table_get_or_create_data(table); + ecs_table_set_size(world, table, data, entity_count); + } +} + +void ecs_eval_component_monitors( + ecs_world_t *world) +{ + eval_component_monitor(world, &world->component_monitors); + eval_component_monitor(world, &world->parent_monitors); +} + +void ecs_merge( + ecs_world_t *world) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + assert(world->is_merging == false); + + bool measure_frame_time = world->measure_frame_time; + + world->is_merging = true; + + ecs_time_t t_start; + if (measure_frame_time) { + ecs_os_get_time(&t_start); + } + + ecs_stage_merge(world, &world->temp_stage); + + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + ecs_stage_merge(world, stage); + }); + + world->is_merging = false; + + ecs_eval_component_monitors(world); + + if (measure_frame_time) { + world->stats.merge_time_total += (FLECS_FLOAT)ecs_time_measure(&t_start); + } + + world->stats.merge_count_total ++; +} + +void ecs_set_automerge( + ecs_world_t *world, + bool auto_merge) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + world->auto_merge = auto_merge; +} + +void ecs_measure_frame_time( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (world->stats.target_fps == 0.0 || enable) { + world->measure_frame_time = enable; + } +} + +void ecs_measure_system_time( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + world->measure_system_time = enable; +} + +/* Increase timer resolution based on target fps */ +static void set_timer_resolution(FLECS_FLOAT fps) +{ + if(fps >= 60.0f) ecs_increase_timer_resolution(1); + else ecs_increase_timer_resolution(0); +} + +void ecs_set_target_fps( + ecs_world_t *world, + FLECS_FLOAT fps) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (!world->arg_fps) { + ecs_measure_frame_time(world, true); + world->stats.target_fps = fps; + set_timer_resolution(fps); + } +} + +void* ecs_get_context( + ecs_world_t *world) +{ + ecs_get_stage(&world); + return world->context; +} + +void ecs_set_context( + ecs_world_t *world, + void *context) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + world->context = context; +} + +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!id_end || id_end > world->stats.last_id, ECS_INVALID_PARAMETER, NULL); + + if (world->stats.last_id < id_start) { + world->stats.last_id = id_start - 1; + } + + world->stats.min_id = id_start; + world->stats.max_id = id_end; +} + +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable) +{ + bool old_value = world->range_check_enabled; + world->range_check_enabled = enable; + return old_value; +} + +int32_t ecs_get_thread_index( + ecs_world_t *world) +{ + if (world->magic == ECS_THREAD_MAGIC) { + ecs_thread_t *thr = (ecs_thread_t*)world; + return thr->index; + } else if (world->magic == ECS_WORLD_MAGIC) { + return 0; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +int32_t ecs_get_threads( + ecs_world_t *world) +{ + return ecs_vector_count(world->workers); +} + +bool ecs_enable_locking( + ecs_world_t *world, + bool enable) +{ + if (enable) { + if (!world->locking_enabled) { + world->mutex = ecs_os_mutex_new(); + world->thr_sync = ecs_os_mutex_new(); + world->thr_cond = ecs_os_cond_new(); + } + } else { + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); + ecs_os_mutex_free(world->thr_sync); + ecs_os_cond_free(world->thr_cond); + } + } + + bool old = world->locking_enabled; + world->locking_enabled = enable; + return old; +} + +void ecs_lock( + ecs_world_t *world) +{ + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->mutex); +} + +void ecs_unlock( + ecs_world_t *world) +{ + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->mutex); +} + +void ecs_begin_wait( + ecs_world_t *world) +{ + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_wait(world->thr_cond, world->thr_sync); +} + +void ecs_end_wait( + ecs_world_t *world) +{ + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->thr_sync); +} + +ecs_c_info_t * ecs_get_c_info( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ROLE_MASK), ECS_INTERNAL_ERROR, NULL); + + if (component < ECS_HI_COMPONENT_ID) { + ecs_c_info_t *c_info = &world->c_info[component]; + if (c_info->component) { + ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL); + return c_info; + } else { + return NULL; + } + } else { + return ecs_map_get(world->t_info, ecs_c_info_t, component); + } +} + +ecs_c_info_t * ecs_get_or_create_c_info( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_c_info_t *c_info = ecs_get_c_info(world, component); + if (!c_info) { + if (component < ECS_HI_COMPONENT_ID) { + c_info = &world->c_info[component]; + ecs_assert(c_info->component == 0, ECS_INTERNAL_ERROR, NULL); + c_info->component = component; + } else { + ecs_c_info_t t_info = { 0 }; + ecs_map_set(world->t_info, component, &t_info); + c_info = ecs_map_get(world->t_info, ecs_c_info_t, component); + ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL); + } + } + + return c_info; +} + +bool ecs_staging_begin( + ecs_world_t *world) +{ + bool in_progress = world->in_progress; + world->in_progress = true; + return in_progress; +} + +void ecs_staging_end( + ecs_world_t *world) +{ + ecs_assert(world->in_progress == true, ECS_INVALID_OPERATION, NULL); + + world->in_progress = false; + if (world->auto_merge) { + ecs_merge(world); + } +} + +static +double insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) +{ + ecs_time_t start = *stop; + double delta_time = ecs_time_measure(stop); + + if (world->stats.target_fps == 0) { + return delta_time; + } + + double target_delta_time = (1.0 / world->stats.target_fps); + double world_sleep_err = + world->stats.sleep_err / (double)world->stats.frame_count_total; + + /* Calculate the time we need to sleep by taking the measured delta from the + * previous frame, and subtracting it from target_delta_time. */ + double sleep = target_delta_time - delta_time; + + /* Pick a sleep interval that is 20 times lower than the time one frame + * should take. This means that this function at most iterates 20 times in + * a busy loop */ + double sleep_time = target_delta_time / 20; + + /* Measure at least two frames before interpreting sleep error */ + if (world->stats.frame_count_total > 1) { + /* If the ratio between the sleep error and the sleep time is too high, + * just do a busy loop */ + if (world_sleep_err / sleep_time > 0.1) { + sleep_time = 0; + } + } + + /* If the time we need to sleep is large enough to warrant a sleep, sleep */ + if (sleep > (sleep_time - world_sleep_err)) { + if (sleep_time > sleep) { + /* Make sure we don't sleep longer than we should */ + sleep_time = sleep; + } + + double sleep_err = 0; + int32_t iterations = 0; + + do { + /* Only call sleep when sleep_time is not 0. On some platforms, even + * a sleep with a timeout of 0 can cause stutter. */ + if (sleep_time != 0) { + ecs_sleepf(sleep_time); + } + + ecs_time_t now = start; + double prev_delta_time = delta_time; + delta_time = ecs_time_measure(&now); + + /* Measure the error of the sleep by taking the difference between + * the time we expected to sleep, and the measured time. This + * assumes that a sleep is less accurate than a high resolution + * timer which should be true in most cases. */ + sleep_err = delta_time - prev_delta_time - sleep_time; + iterations ++; + } while ((target_delta_time - delta_time) > (sleep_time - world_sleep_err)); + + /* Add sleep error measurement to sleep error, with a bias towards the + * latest measured values. */ + world->stats.sleep_err = (FLECS_FLOAT) + (world_sleep_err * 0.9 + sleep_err * 0.1) * + (FLECS_FLOAT)world->stats.frame_count_total; + } + + /* Make last minute corrections if due to a larger clock error delta_time + * is still more than 5% away from the target. The 5% buffer is to account + * for the fact that measuring the time also takes time. */ + while (delta_time < target_delta_time * 0.95) { + ecs_time_t now = start; + delta_time = ecs_time_measure(&now); + } + + return delta_time; +} + +static +FLECS_FLOAT start_measure_frame( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + double delta_time = 0; + + if (world->measure_frame_time || (user_delta_time == 0)) { + ecs_time_t t = world->frame_start_time; + do { + if (world->frame_start_time.sec) { + delta_time = insert_sleep(world, &t); + + ecs_time_measure(&t); + } else { + ecs_time_measure(&t); + if (world->stats.target_fps != 0) { + delta_time = 1.0 / world->stats.target_fps; + } else { + delta_time = 1.0 / 60.0; /* Best guess */ + } + } + + /* Keep trying while delta_time is zero */ + } while (delta_time == 0); + + world->frame_start_time = t; + + /* Keep track of total time passed in world */ + world->stats.world_time_total_raw += (FLECS_FLOAT)delta_time; + } + + return (FLECS_FLOAT)delta_time; +} + +static +void stop_measure_frame( + ecs_world_t* world) +{ + if (world->measure_frame_time) { + ecs_time_t t = world->frame_start_time; + world->stats.frame_time_total += (FLECS_FLOAT)ecs_time_measure(&t); + } +} + +FLECS_FLOAT ecs_frame_begin( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(world->in_progress == false, ECS_INVALID_OPERATION, NULL); + + ecs_assert(user_delta_time != 0 || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); + + if (world->locking_enabled) { + ecs_lock(world); + } + + /* Start measuring total frame time */ + FLECS_FLOAT delta_time = start_measure_frame(world, user_delta_time); + if (user_delta_time == 0) { + user_delta_time = delta_time; + } + + world->stats.delta_time_raw = user_delta_time; + world->stats.delta_time = user_delta_time * world->stats.time_scale; + + /* Keep track of total scaled time passed in world */ + world->stats.world_time_total += world->stats.delta_time; + + ecs_eval_component_monitors(world); + + return user_delta_time; +} + +void ecs_frame_end( + ecs_world_t *world) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(world->in_progress == false, ECS_INVALID_OPERATION, NULL); + + world->stats.frame_count_total ++; + + ecs_stage_merge_post_frame(world, &world->temp_stage); + + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + ecs_stage_merge_post_frame(world, stage); + }); + + if (world->locking_enabled) { + ecs_unlock(world); + + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_broadcast(world->thr_cond); + ecs_os_mutex_unlock(world->thr_sync); + } + + stop_measure_frame(world); +} + +const ecs_world_info_t* ecs_get_world_info( + ecs_world_t *world) +{ + return &world->stats; +} + +void ecs_notify_queries( + ecs_world_t *world, + ecs_query_event_t *event) +{ + int32_t i, count = ecs_vector_count(world->queries); + ecs_query_t **queries = ecs_vector_first(world->queries, ecs_query_t*); + + for (i = 0; i < count; i ++) { + ecs_query_notify(world, queries[i], event); + } +} + +void ecs_delete_table( + ecs_world_t *world, + ecs_table_t *table) +{ + /* Notify queries that table is to be removed */ + ecs_notify_queries( + world, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + + uint32_t id = table->id; + + /* Free resources associated with table */ + ecs_table_free(world, table); + + /* Remove table from sparse set */ + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_sparse_remove(world->store.tables, id); + + /* Don't do generations as we want table ids to remain 32 bit */ + ecs_sparse_set_generation(world->store.tables, id); +} + +static +ecs_switch_header_t *get_header( + const ecs_switch_t *sw, + uint64_t value) +{ + if (value == 0) { + return NULL; + } + + value = (uint32_t)value; + + ecs_assert(value >= sw->min, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value <= sw->max, ECS_INTERNAL_ERROR, NULL); + + uint64_t index = value - sw->min; + + return &sw->headers[index]; +} + +static +void remove_node( + ecs_switch_header_t *hdr, + ecs_switch_node_t *nodes, + ecs_switch_node_t *node, + int32_t element) +{ + /* The node is currently assigned to a value */ + if (hdr->element == element) { + ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); + /* If this is the first node, update the header */ + hdr->element = node->next; + } else { + /* If this is not the first node, update the previous node to the + * removed node's next ptr */ + ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); + ecs_switch_node_t *prev_node = &nodes[node->prev]; + prev_node->next = node->next; + } + + int32_t next = node->next; + if (next != -1) { + ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); + /* If this is not the last node, update the next node to point to the + * removed node's prev ptr */ + ecs_switch_node_t *next_node = &nodes[next]; + next_node->prev = node->prev; + } + + /* Decrease count of current header */ + hdr->count --; + ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); +} + +ecs_switch_t* ecs_switch_new( + uint64_t min, + uint64_t max, + int32_t elements) +{ + ecs_assert(min != max, ECS_INVALID_PARAMETER, NULL); + + /* Min must be larger than 0, as 0 is an invalid entity id, and should + * therefore never occur as case id */ + ecs_assert(min > 0, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); + result->min = (uint32_t)min; + result->max = (uint32_t)max; + + int32_t count = (int32_t)(max - min) + 1; + result->headers = ecs_os_calloc(ECS_SIZEOF(ecs_switch_header_t) * count); + result->nodes = ecs_vector_new(ecs_switch_node_t, elements); + result->values = ecs_vector_new(uint64_t, elements); + + int64_t i; + for (i = 0; i < count; i ++) { + result->headers[i].element = -1; + result->headers[i].count = 0; + } + + ecs_switch_node_t *nodes = ecs_vector_first( + result->nodes, ecs_switch_node_t); + uint64_t *values = ecs_vector_first( + result->values, uint64_t); + + for (i = 0; i < elements; i ++) { + nodes[i].prev = -1; + nodes[i].next = -1; + values[i] = 0; + } + + return result; +} + +void ecs_switch_free( + ecs_switch_t *sw) +{ + ecs_os_free(sw->headers); + ecs_vector_free(sw->nodes); + ecs_vector_free(sw->values); + ecs_os_free(sw); +} + +void ecs_switch_add( + ecs_switch_t *sw) +{ + ecs_switch_node_t *node = ecs_vector_add(&sw->nodes, ecs_switch_node_t); + uint64_t *value = ecs_vector_add(&sw->values, uint64_t); + node->prev = -1; + node->next = -1; + *value = 0; +} + +void ecs_switch_set_count( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count == count) { + return; + } + + ecs_vector_set_count(&sw->nodes, ecs_switch_node_t, count); + ecs_vector_set_count(&sw->values, uint64_t, count); + + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + + int32_t i; + for (i = old_count; i < count; i ++) { + ecs_switch_node_t *node = &nodes[i]; + node->prev = -1; + node->next = -1; + values[i] = 0; + } +} + +void ecs_switch_ensure( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count >= count) { + return; + } + + ecs_switch_set_count(sw, count); +} + +void ecs_switch_addn( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + ecs_switch_set_count(sw, old_count + count); +} + +void ecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t cur_value = values[element]; + + /* If the node is already assigned to the value, nothing to be done */ + if (cur_value == value) { + return; + } + + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + ecs_switch_node_t *node = &nodes[element]; + + ecs_switch_header_t *cur_hdr = get_header(sw, cur_value); + ecs_switch_header_t *dst_hdr = get_header(sw, value); + + /* If value is not 0, and dst_hdr is NULL, then this is not a valid value + * for this switch */ + ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); + + if (cur_hdr) { + remove_node(cur_hdr, nodes, node, element); + } + + /* Now update the node itself by adding it as the first node of dst */ + node->prev = -1; + values[element] = value; + + if (dst_hdr) { + node->next = dst_hdr->element; + + /* Also update the dst header */ + int32_t first = dst_hdr->element; + if (first != -1) { + ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_switch_node_t *first_node = &nodes[first]; + first_node->prev = element; + } + + dst_hdr->element = element; + dst_hdr->count ++; + } +} + +void ecs_switch_remove( + ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t value = values[element]; + ecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, ecs_switch_node_t); + ecs_switch_node_t *node = &nodes[element]; + + /* If node is currently assigned to a case, remove it from the list */ + if (value != 0) { + ecs_switch_header_t *hdr = get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); + remove_node(hdr, nodes, node, element); + } + + /* Remove element from arrays */ + ecs_vector_remove_index(sw->nodes, ecs_switch_node_t, element); + ecs_vector_remove_index(sw->values, uint64_t, element); + + /* When the element was removed and the list was not empty, the last element + * of the list got moved to the location of the removed node. Update the + * linked list so that nodes that previously pointed to the last element + * point to the moved node. + * + * The 'node' variable is guaranteed to point to the moved element, if the + * nodes list is not empty. + * + * If count is equal to the removed index, nothing needs to be done. + */ + int32_t count = ecs_vector_count(sw->nodes); + if (count != 0 && count != element) { + int32_t prev = node->prev; + if (prev != -1) { + /* If the former last node was not the first node, update its + * prev to point to its new index, which is the index of the removed + * element. */ + ecs_assert(prev >= 0, ECS_INVALID_PARAMETER, NULL); + nodes[prev].next = element; + } else { + /* If the former last node was the first node of its kind, find the + * header for the value of the node. The header should have at + * least one element. */ + ecs_switch_header_t *hdr = get_header(sw, values[element]); + if (hdr && hdr->element != -1) { + ecs_assert(hdr->element == ecs_vector_count(sw->nodes), + ECS_INTERNAL_ERROR, NULL); + hdr->element = element; + } + } + } +} + +uint64_t ecs_switch_get( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + return values[element]; +} + +ecs_vector_t* ecs_switch_values( + const ecs_switch_t *sw) +{ + return sw->values; +} + +int32_t ecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_switch_header_t *hdr = get_header(sw, value); + if (!hdr) { + return 0; + } + + return hdr->count; +} + +void ecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2) +{ + uint64_t v1 = ecs_switch_get(sw, elem_1); + uint64_t v2 = ecs_switch_get(sw, elem_2); + + ecs_switch_set(sw, elem_2, v1); + ecs_switch_set(sw, elem_1, v2); +} + +int32_t ecs_switch_first( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert((uint32_t)value <= sw->max, ECS_INVALID_PARAMETER, NULL); + ecs_assert((uint32_t)value >= sw->min, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_header_t *hdr = get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + + return hdr->element; +} + +int32_t ecs_switch_next( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_node_t *nodes = ecs_vector_first( + sw->nodes, ecs_switch_node_t); + + return nodes[element].next; +} + +#ifndef _MSC_VER +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + http://burtleburtle.net/bob/c/lookup3.c +------------------------------------------------------------------------------- +*/ + +#ifdef _MSC_VER +//FIXME +#else +#include /* attempt to define endianness */ +#endif +#ifdef linux +# include /* attempt to define endianness */ +#endif + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +#else +# define HASH_LITTLE_ENDIAN 0 +#endif + +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + + +/* + * hashlittle2: return 2 32-bit hash values + * + * This is identical to hashlittle(), except it returns two 32-bit hash + * values instead of just one. This is good enough for hash table + * lookup with 2^^64 buckets, or if you want a second hash if you're not + * happy with the first, or if you want a probably-unique 64-bit ID for + * the key. *pc is better mixed than *pb, so use *pc first. If you want + * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". + */ +static +void hashlittle2( + const void *key, /* the key to hash */ + size_t length, /* length of the key */ + uint32_t *pc, /* IN: primary initval, OUT: primary hash */ + uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +{ + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; + + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + const uint8_t *k8; + (void)k8; + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + } + + final(a,b,c); + *pc=c; *pb=b; +} + +void ecs_hash( + const void *data, + ecs_size_t length, + uint64_t *result) +{ + uint32_t h_1 = 0; + uint32_t h_2 = 0; + + hashlittle2( + data, + ecs_to_size_t(length), + &h_1, + &h_2); + + *result = h_1 | ((uint64_t)h_2 << 32); +} + + +ecs_iter_t ecs_filter_iter( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_filter_iter_t iter = { + .filter = filter ? *filter : (ecs_filter_t){0}, + .tables = world->store.tables, + .index = 0 + }; + + return (ecs_iter_t){ + .world = world, + .iter.filter = iter + }; +} + +bool ecs_filter_next( + ecs_iter_t *it) +{ + ecs_filter_iter_t *iter = &it->iter.filter; + ecs_sparse_t *tables = iter->tables; + int32_t count = ecs_sparse_count(tables); + int32_t i; + + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = ecs_table_get_data(table); + + if (!data) { + continue; + } + + if (!ecs_table_match_filter(it->world, table, &iter->filter)) { + continue; + } + + iter->table.table = table; + it->table = &iter->table; + it->table_columns = data->columns; + it->count = ecs_table_count(table); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + iter->index = i + 1; + + return true; + } + + return false; +} + + +static +void ensure( + ecs_bitset_t *bs, + ecs_size_t size) +{ + if (!bs->size) { + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + bs->data = ecs_os_calloc(new_size); + } else if (size > bs->size) { + int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->data = ecs_os_realloc(bs->data, new_size); + ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); + } +} + +void ecs_bitset_init( + ecs_bitset_t* bs) +{ + bs->size = 0; + bs->count = 0; + bs->data = NULL; +} + +void ecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count) +{ + if (count > bs->count) { + bs->count = count; + ensure(bs, count); + } +} + +void ecs_bitset_deinit( + ecs_bitset_t *bs) +{ + ecs_os_free(bs->data); +} + +void ecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count) +{ + int32_t elem = bs->count += count; + ensure(bs, elem); +} + +void ecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value) +{ + ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t hi = elem >> 6; + int32_t lo = elem & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +} + +bool ecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) +{ + ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +} + +int32_t ecs_bitset_count( + const ecs_bitset_t *bs) +{ + return bs->count; +} + +void ecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem) +{ + ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t last = bs->count - 1; + bool last_value = ecs_bitset_get(bs, last); + ecs_bitset_set(bs, elem, last_value); + bs->count --; +} + +void ecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) +{ + ecs_assert(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); + + bool a = ecs_bitset_get(bs, elem_a); + bool b = ecs_bitset_get(bs, elem_b); + ecs_bitset_set(bs, elem_a, b); + ecs_bitset_set(bs, elem_b, a); +} + +/* Add an extra element to the buffer */ +static +void ecs_strbuf_grow( + ecs_strbuf_t *b) +{ + /* Allocate new element */ + ecs_strbuf_element_embedded *e = ecs_os_malloc(sizeof(ecs_strbuf_element_embedded)); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = true; + e->super.buf = e->buf; + e->super.pos = 0; + e->super.next = NULL; +} + +/* Add an extra dynamic element */ +static +void ecs_strbuf_grow_str( + ecs_strbuf_t *b, + char *str, + char *alloc_str, + int32_t size) +{ + /* Allocate new element */ + ecs_strbuf_element_str *e = ecs_os_malloc(sizeof(ecs_strbuf_element_str)); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = false; + e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); + e->super.next = NULL; + e->super.buf = str; + e->alloc_str = alloc_str; +} + +static +char* ecs_strbuf_ptr( + ecs_strbuf_t *b) +{ + if (b->buf) { + return &b->buf[b->current->pos]; + } else { + return &b->current->buf[b->current->pos]; + } +} + +/* Compute the amount of space left in the current element */ +static +int32_t ecs_strbuf_memLeftInCurrentElement( + ecs_strbuf_t *b) +{ + if (b->current->buffer_embedded) { + return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; + } else { + return 0; + } +} + +/* Compute the amount of space left */ +static +int32_t ecs_strbuf_memLeft( + ecs_strbuf_t *b) +{ + if (b->max) { + return b->max - b->size - b->current->pos; + } else { + return INT_MAX; + } +} + +static +void ecs_strbuf_init( + ecs_strbuf_t *b) +{ + /* Initialize buffer structure only once */ + if (!b->elementCount) { + b->size = 0; + b->firstElement.super.next = NULL; + b->firstElement.super.pos = 0; + b->firstElement.super.buffer_embedded = true; + b->firstElement.super.buf = b->firstElement.buf; + b->elementCount ++; + b->current = (ecs_strbuf_element*)&b->firstElement; + } +} + +/* Quick custom function to copy a maxium number of characters and + * simultaneously determine length of source string. */ +static +int32_t fast_strncpy( + char * dst, + const char * src, + int n_cpy, + int n) +{ + ecs_assert(n_cpy >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(n >= 0, ECS_INTERNAL_ERROR, NULL); + + const char *ptr, *orig = src; + char ch; + + for (ptr = src; (ptr - orig < n) && (ch = *ptr); ptr ++) { + if (ptr - orig < n_cpy) { + *dst = ch; + dst ++; + } + } + + ecs_assert(ptr - orig < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + + return (int32_t)(ptr - orig); +} + +/* Append a format string to a buffer */ +static +bool ecs_strbuf_vappend_intern( + ecs_strbuf_t *b, + const char* str, + va_list args) +{ + bool result = true; + va_list arg_cpy; + + if (!str) { + return result; + } + + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + + if (!memLeft) { + return false; + } + + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; + + va_copy(arg_cpy, args); + memRequired = vsnprintf( + ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); + + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* If string is a format string, a new buffer of size memRequired is + * needed to re-evaluate the format string and only use the part that + * wasn't already copied to the previous element */ + if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { + /* Resulting string fits in standard-size buffer. Note that the + * entire string needs to fit, not just the remainder, as the + * format string cannot be partially evaluated */ + ecs_strbuf_grow(b); + + /* Copy entire string to new buffer */ + ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); + + /* Ignore the part of the string that was copied into the + * previous buffer. The string copied into the new buffer could + * be memmoved so that only the remainder is left, but that is + * most likely more expensive than just keeping the entire + * string. */ + + /* Update position in buffer */ + b->current->pos += memRequired; + } else { + /* Resulting string does not fit in standard-size buffer. + * Allocate a new buffer that can hold the entire string. */ + char *dst = ecs_os_malloc(memRequired + 1); + ecs_os_vsprintf(dst, str, arg_cpy); + ecs_strbuf_grow_str(b, dst, dst, memRequired); + } + } else { + /* Buffer max has been reached */ + result = false; + } + + va_end(arg_cpy); + + return result; +} + +static +bool ecs_strbuf_append_intern( + ecs_strbuf_t *b, + const char* str, + int n) +{ + bool result = true; + + if (!str) { + return result; + } + + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + + if (memLeft <= 0) { + return false; + } + + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; + + if (n < 0) n = INT_MAX; + + memRequired = fast_strncpy(ecs_strbuf_ptr(b), str, max_copy, n); + + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* Element was not large enough, but buffer still has space */ + b->current->pos += memLeftInElement; + memRequired -= memLeftInElement; + + /* Current element was too small, copy remainder into new element */ + if (memRequired < ECS_STRBUF_ELEMENT_SIZE) { + /* A standard-size buffer is large enough for the new string */ + ecs_strbuf_grow(b); + + /* Copy the remainder to the new buffer */ + if (n) { + /* If a max number of characters to write is set, only a + * subset of the string should be copied to the buffer */ + ecs_os_strncpy( + ecs_strbuf_ptr(b), + str + memLeftInElement, + (size_t)memRequired); + } else { + ecs_os_strcpy(ecs_strbuf_ptr(b), str + memLeftInElement); + } + + /* Update to number of characters copied to new buffer */ + b->current->pos += memRequired; + } else { + char *remainder = ecs_os_strdup(str + memLeftInElement); + ecs_strbuf_grow_str(b, remainder, remainder, memRequired); + } + } else { + /* Buffer max has been reached */ + result = false; + } + + return result; +} + +bool ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) +{ + bool result = ecs_strbuf_vappend_intern( + b, fmt, args + ); + + return result; +} + +bool ecs_strbuf_append( + ecs_strbuf_t *b, + const char* fmt, + ...) +{ + va_list args; + va_start(args, fmt); + bool result = ecs_strbuf_vappend_intern( + b, fmt, args + ); + va_end(args); + + return result; +} + +bool ecs_strbuf_appendstrn( + ecs_strbuf_t *b, + const char* str, + int32_t len) +{ + return ecs_strbuf_append_intern( + b, str, len + ); +} + +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *b, + char* str) +{ + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, str, str, 0); + return true; +} + +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *b, + const char* str) +{ + /* Removes const modifier, but logic prevents changing / delete string */ + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, (char*)str, NULL, 0); + return true; +} + +bool ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) +{ + return ecs_strbuf_append_intern( + b, str, -1 + ); +} + +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer) +{ + if (src_buffer->elementCount) { + if (src_buffer->buf) { + return ecs_strbuf_appendstr(dst_buffer, src_buffer->buf); + } else { + ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; + + /* Copy first element as it is inlined in the src buffer */ + ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); + + while ((e = e->next)) { + dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); + *dst_buffer->current->next = *e; + } + } + + *src_buffer = ECS_STRBUF_INIT; + } + + return true; +} + +char* ecs_strbuf_get(ecs_strbuf_t *b) { + char* result = NULL; + + if (b->elementCount) { + if (b->buf) { + b->buf[b->current->pos] = '\0'; + result = ecs_os_strdup(b->buf); + } else { + void *next = NULL; + int32_t len = b->size + b->current->pos + 1; + + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + + result = ecs_os_malloc(len); + char* ptr = result; + + do { + ecs_os_memcpy(ptr, e->buf, e->pos); + ptr += e->pos; + next = e->next; + if (e != &b->firstElement.super) { + if (!e->buffer_embedded) { + ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); + } + ecs_os_free(e); + } + } while ((e = next)); + + result[len - 1] = '\0'; + } + } else { + result = NULL; + } + + b->elementCount = 0; + + return result; +} + +void ecs_strbuf_reset(ecs_strbuf_t *b) { + if (b->elementCount && !b->buf) { + void *next = NULL; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + do { + next = e->next; + if (e != (ecs_strbuf_element*)&b->firstElement) { + ecs_os_free(e); + } + } while ((e = next)); + } + + *b = ECS_STRBUF_INIT; +} + +void ecs_strbuf_list_push( + ecs_strbuf_t *buffer, + const char *list_open, + const char *separator) +{ + buffer->list_sp ++; + buffer->list_stack[buffer->list_sp].count = 0; + buffer->list_stack[buffer->list_sp].separator = separator; + + if (list_open) { + ecs_strbuf_appendstr(buffer, list_open); + } +} + +void ecs_strbuf_list_pop( + ecs_strbuf_t *buffer, + const char *list_close) +{ + buffer->list_sp --; + + if (list_close) { + ecs_strbuf_appendstr(buffer, list_close); + } +} + +void ecs_strbuf_list_next( + ecs_strbuf_t *buffer) +{ + int32_t list_sp = buffer->list_sp; + if (buffer->list_stack[list_sp].count != 0) { + ecs_strbuf_appendstr(buffer, buffer->list_stack[list_sp].separator); + } + buffer->list_stack[list_sp].count ++; +} + +bool ecs_strbuf_list_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...) +{ + ecs_strbuf_list_next(buffer); + + va_list args; + va_start(args, fmt); + bool result = ecs_strbuf_vappend_intern( + buffer, fmt, args + ); + va_end(args); + + return result; +} + +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *buffer, + const char *str) +{ + ecs_strbuf_list_next(buffer); + return ecs_strbuf_appendstr(buffer, str); +} + +#define ECS_ANNOTATION_LENGTH_MAX (16) + +#define TOK_SOURCE ':' +#define TOK_AND ',' +#define TOK_OR "||" +#define TOK_NOT '!' +#define TOK_OPTIONAL '?' +#define TOK_ROLE '|' +#define TOK_TRAIT '>' +#define TOK_FOR "FOR" +#define TOK_NAME_SEP '.' +#define TOK_ANNOTATE_OPEN '[' +#define TOK_ANNOTATE_CLOSE ']' +#define TOK_WILDCARD '*' +#define TOK_SINGLETON '$' + +#define TOK_ANY "ANY" +#define TOK_OWNED "OWNED" +#define TOK_SHARED "SHARED" +#define TOK_SYSTEM "SYSTEM" +#define TOK_PARENT "PARENT" +#define TOK_CASCADE "CASCADE" + +#define TOK_ROLE_CHILDOF "CHILDOF" +#define TOK_ROLE_INSTANCEOF "INSTANCEOF" +#define TOK_ROLE_TRAIT "TRAIT" +#define TOK_ROLE_AND "AND" +#define TOK_ROLE_OR "OR" +#define TOK_ROLE_XOR "XOR" +#define TOK_ROLE_NOT "NOT" +#define TOK_ROLE_SWITCH "SWITCH" +#define TOK_ROLE_CASE "CASE" + +#define TOK_IN "in" +#define TOK_OUT "out" +#define TOK_INOUT "inout" + +#define ECS_MAX_TOKEN_SIZE (256) + +typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; + +/** Skip spaces when parsing signature */ +static +const char *skip_space( + const char *ptr) +{ + while (isspace(*ptr)) { + ptr ++; + } + return ptr; +} + +static +int entity_compare( + const void *ptr1, + const void *ptr2) +{ + ecs_entity_t e1 = *(ecs_entity_t*)ptr1; + ecs_entity_t e2 = *(ecs_entity_t*)ptr2; + return (e1 > e2) - (e1 < e2); +} + +static +void vec_add_entity( + ecs_vector_t **vec, + ecs_entity_t entity) +{ + ecs_entity_t *e = ecs_vector_add(vec, ecs_entity_t); + *e = entity; + + /* Keep array sorted so that we can use it in type compare operations */ + ecs_vector_sort(*vec, ecs_entity_t, entity_compare); +} + + +/* -- Private functions -- */ + +static +bool valid_identifier_char( + char ch) +{ + if (ch && (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.' || + ch == TOK_SINGLETON || ch == TOK_WILDCARD)) + { + return true; + } + + return false; +} + +static +bool valid_operator_char( + char ch) +{ + if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + return true; + } + + return false; +} + +static +const char* parse_identifier( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + char *token_out) +{ + ptr = skip_space(ptr); + + char *tptr = token_out, ch = ptr[0]; + + if (!valid_identifier_char(ch)) { + ecs_parser_error(name, sig, column, "invalid identifier", ptr); + return NULL; + } + + for (; (ch = *ptr); ptr ++) { + if (!valid_identifier_char(ch)) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + return skip_space(ptr); +} + +static +ecs_entity_t parse_role( + const char *name, + const char *sig, + int64_t column, + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_ROLE_CHILDOF)) { + return ECS_CHILDOF; + } else if (!ecs_os_strcmp(token, TOK_ROLE_INSTANCEOF)) { + return ECS_INSTANCEOF; + } else if (!ecs_os_strcmp(token, TOK_ROLE_TRAIT)) { + return ECS_TRAIT; + } else if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { + return ECS_AND; + } else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { + return ECS_OR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_XOR)) { + return ECS_XOR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { + return ECS_NOT; + } else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) { + return ECS_SWITCH; + } else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) { + return ECS_CASE; + } else if (!ecs_os_strcmp(token, TOK_OWNED)) { + return ECS_OWNED; + } else { + ecs_parser_error(name, sig, column, "invalid role '%s'", token); + return 0; + } +} + +static +ecs_sig_from_kind_t parse_source( + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_PARENT)) { + return EcsFromParent; + } else if (!ecs_os_strcmp(token, TOK_SYSTEM)) { + return EcsFromSystem; + } else if (!ecs_os_strcmp(token, TOK_ANY)) { + return EcsFromAny; + } else if (!ecs_os_strcmp(token, TOK_OWNED)) { + return EcsFromOwned; + } else if (!ecs_os_strcmp(token, TOK_SHARED)) { + return EcsFromShared; + } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { + return EcsCascade; + } else { + return EcsFromEntity; + } +} + +static +ecs_sig_oper_kind_t parse_operator( + char ch) +{ + if (ch == TOK_OPTIONAL) { + return EcsOperOptional; + } else if (ch == TOK_NOT) { + return EcsOperNot; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +static +const char* parse_annotation( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + ecs_sig_inout_kind_t *inout_kind_out) +{ + char token[ECS_MAX_TOKEN_SIZE]; + + ptr = parse_identifier(name, sig, column, ptr, token); + if (!ptr) { + return NULL; + } + + if (!strcmp(token, "in")) { + *inout_kind_out = EcsIn; + } else + if (!strcmp(token, "out")) { + *inout_kind_out = EcsOut; + } else + if (!strcmp(token, "inout")) { + *inout_kind_out = EcsInOut; + } + + ptr = skip_space(ptr); + + if (ptr[0] != TOK_ANNOTATE_CLOSE) { + ecs_parser_error(name, sig, column, "expected ]"); + return NULL; + } + + return ptr + 1; +} + +typedef struct sig_element_t { + ecs_entity_t role; + ecs_sig_inout_kind_t inout_kind; + ecs_sig_from_kind_t from_kind; + ecs_sig_oper_kind_t oper_kind; + char *trait; + char *source; + char *component; + char *name; +} sig_element_t; + +static +const char* parse_element( + const char *name, + const char *sig, + sig_element_t *elem_out) +{ + bool explicit_inout = false; + const char *ptr = sig; + char token[ECS_MAX_TOKEN_SIZE] = {0}; + sig_element_t elem = { + .inout_kind = EcsInOut, + .from_kind = EcsFromOwned, + .oper_kind = EcsOperAnd + }; + + ptr = skip_space(ptr); + + /* Inout specifiers always come first */ + if (ptr[0] == TOK_ANNOTATE_OPEN) { + explicit_inout = true; + ptr = parse_annotation(name, sig, (ptr - sig), ptr + 1, &elem.inout_kind); + if (!ptr) { + return NULL; + } + ptr = skip_space(ptr); + } + + if (valid_operator_char(ptr[0])) { + elem.oper_kind = parse_operator(ptr[0]); + ptr = skip_space(ptr + 1); + } + + /* If next token is the start of an identifier, it could be either a type + * role, source or component identifier */ + if (valid_identifier_char(ptr[0])) { + ptr = parse_identifier(name, sig, (ptr - sig), ptr, token); + if (!ptr) { + return NULL; + } + + /* Is token a source identifier? */ + if (ptr[0] == TOK_SOURCE) { + ptr ++; + goto parse_source; + } + + /* Is token a type role? */ + if (ptr[0] == TOK_ROLE && ptr[1] != TOK_ROLE) { + ptr ++; + goto parse_role; + } + + /* Is token a trait? (using shorthand notation) */ + if (!ecs_os_strncmp(ptr, TOK_FOR, 3)) { + elem.role = ECS_TRAIT; + ptr += 3; + goto parse_trait; + } + + /* If it is neither, the next token must be a component */ + goto parse_component; + + /* If next token is the source token, this is an empty source */ + } else if (ptr[0] == TOK_SOURCE) { + goto empty_source; + + /* Nothing else expected here */ + } else { + ecs_parser_error(name, sig, (ptr - sig), + "unexpected character '%c'", ptr[0]); + return NULL; + } + +empty_source: + elem.from_kind = EcsFromEmpty; + ptr = skip_space(ptr + 1); + if (valid_identifier_char(ptr[0])) { + ptr = parse_identifier(name, sig, (ptr - sig), ptr, token); + if (!ptr) { + return NULL; + } + + goto parse_component; + } else { + ecs_parser_error(name, sig, (ptr - sig), + "expected identifier after source operator"); + return NULL; + } + +parse_source: + elem.from_kind = parse_source(token); + if (elem.from_kind == EcsFromEntity) { + elem.source = ecs_os_strdup(token); + } + + ptr = skip_space(ptr); + if (valid_identifier_char(ptr[0])) { + ptr = parse_identifier(name, sig, (ptr - sig), ptr, token); + if (!ptr) { + return NULL; + } + + /* Is the next token a role? */ + if (ptr[0] == TOK_ROLE && ptr[1] != TOK_ROLE) { + ptr++; + goto parse_role; + } + + /* Is token a trait? (using shorthand notation) */ + if (!ecs_os_strncmp(ptr, TOK_FOR, 3)) { + elem.role = ECS_TRAIT; + ptr += 3; + goto parse_trait; + } + + /* If not, it's a component */ + goto parse_component; + } else { + ecs_parser_error(name, sig, (ptr - sig), + "expected identifier after source"); + return NULL; + } + +parse_role: + elem.role = parse_role(name, sig, (ptr - sig), token); + if (!elem.role) { + return NULL; + } + + ptr = skip_space(ptr); + + /* If next token is the source token, this is an empty source */ + if (valid_identifier_char(ptr[0])) { + ptr = parse_identifier(name, sig, (ptr - sig), ptr, token); + if (!ptr) { + return NULL; + } + + /* Is token a trait? */ + if (ptr[0] == TOK_TRAIT) { + ptr ++; + goto parse_trait; + } + + /* If not, it's a component */ + goto parse_component; + } else { + ecs_parser_error(name, sig, (ptr - sig), + "expected identifier after role"); + return NULL; + } + +parse_trait: + elem.trait = ecs_os_strdup(token); + + ptr = skip_space(ptr); + if (valid_identifier_char(ptr[0])) { + ptr = parse_identifier(name, sig, (ptr - sig), ptr, token); + if (!ptr) { + return NULL; + } + + /* Can only be a component */ + goto parse_component; + } else { + ecs_parser_error(name, sig, (ptr - sig), + "expected identifier after trait"); + return NULL; + } + +parse_component: + elem.component = ecs_os_strdup(token); + + ptr = skip_space(ptr); + if (valid_identifier_char(ptr[0])) { + ptr = parse_identifier(name, sig, (ptr - sig), ptr, token); + if (!ptr) { + return NULL; + } + + /* Can only be a name */ + goto parse_name; + } else { + /* If nothing else, parsing of this element is done */ + goto parse_done; + } + +parse_name: + elem.name = ecs_os_strdup(token); + ptr = skip_space(ptr); + +parse_done: + if (ptr[0] != TOK_AND && ecs_os_strncmp(ptr, TOK_OR, 2) && ptr[0]) { + ecs_parser_error(name, sig, (ptr - sig), + "expected end of expression or next element"); + return NULL; + } + + if (!ecs_os_strcmp(elem.component, "0")) { + if (ptr[0]) { + ecs_parser_error(name, sig, (ptr - sig), + "unexpected element after 0"); + return NULL; + } + + if (elem.from_kind != EcsFromOwned) { + ecs_parser_error(name, sig, (ptr - sig), + "invalid source modifier for 0"); + return NULL; + } + } + + if (!explicit_inout) { + if (elem.from_kind != EcsFromOwned) { + elem.inout_kind = EcsIn; + } + } + + *elem_out = elem; + + return ptr; +} + +int ecs_parse_expr( + ecs_world_t *world, + const char *name, + const char *sig, + ecs_parse_action_t action, + void *ctx) +{ + sig_element_t elem; + + bool is_or = false; + const char *ptr = sig; + while ((ptr = parse_element(name, ptr, &elem))) { + if (is_or) { + ecs_assert(elem.oper_kind == EcsOperAnd, ECS_INVALID_SIGNATURE, sig); + elem.oper_kind = EcsOperOr; + } + + if (action(world, name, sig, ptr - sig, + elem.from_kind, elem.oper_kind, elem.inout_kind, elem.role, + elem.component, elem.source, elem.trait, elem.name, ctx)) + { + if (!name) { + return -1; + } + + ecs_abort(ECS_INVALID_SIGNATURE, sig); + } + + ecs_os_free(elem.component); + ecs_os_free(elem.source); + ecs_os_free(elem.trait); + ecs_os_free(elem.name); + + is_or = false; + if (!strncmp(ptr, TOK_OR, 2)) { + is_or = true; + if (elem.from_kind == EcsFromEmpty) { + ecs_parser_error(name, sig, ptr - sig, + "invalid empty source in or expression"); + return -1; + } + + if (elem.from_kind == EcsFromSystem) { + ecs_parser_error(name, sig, ptr - sig, + "invalid system source in or expression"); + return -1; + } + } + + if (ptr[0]) { + ptr ++; + if (is_or) { + ptr ++; + } + } + + ptr = skip_space(ptr); + + if (!ptr[0]) { + break; + } + } + + if (!ptr) { + if (!name) { + return -1; + } + + ecs_abort(ECS_INVALID_SIGNATURE, sig); + } + + return 0; +} + +/** Parse callback that adds component to the components array for a system */ +static +int ecs_parse_signature_action( + ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + ecs_sig_from_kind_t from_kind, + ecs_sig_oper_kind_t oper_kind, + ecs_sig_inout_kind_t inout_kind, + ecs_entity_t role, + const char *entity_id, + const char *source_id, + const char *trait_id, + const char *arg_name, + void *data) +{ + ecs_sig_t *sig = data; + bool is_singleton = false; + + ecs_assert(sig != NULL, ECS_INTERNAL_ERROR, NULL); + + if (entity_id[0] == '$') { + if (from_kind == EcsFromEntity) { + ecs_parser_error(name, expr, column, + "singleton component '%s' cannot have a source", entity_id); + } + + from_kind = EcsFromEntity; + is_singleton = true; + entity_id ++; + } + + /* Lookup component handle by string identifier */ + ecs_entity_t source = 0, component = ecs_lookup_fullpath(world, entity_id); + if (!component) { + /* "0" is a valid expression used to indicate that a system matches no + * components */ + if (!strcmp(entity_id, "0")) { + /* No need to add 0 component to signature */ + return 0; + } else { + ecs_parser_error(name, expr, column, + "unresolved component identifier '%s'", entity_id); + } + } + + if (is_singleton) { + source = component; + } + + /* Lookup trait handle by string identifier */ + if (trait_id) { + ecs_entity_t trait = ecs_lookup_fullpath(world, trait_id); + if (!trait) { + ecs_parser_error(name, expr, column, + "unresolved trait identifier '%s'", trait_id); + } else { + component = ecs_entity_t_comb(component, trait); + } + } + + component |= role; + + if (!source && from_kind == EcsFromEntity) { + source = ecs_lookup_fullpath(world, source_id); + if (!source) { + ecs_parser_error(name, expr, column, + "unresolved source identifier '%s'", source_id); + } + } + + return ecs_sig_add( + world, sig, from_kind, oper_kind, inout_kind, component, source, + arg_name); +} + +void ecs_sig_init( + ecs_world_t *world, + const char *name, + const char *expr, + ecs_sig_t *sig) +{ + if (expr && ecs_os_strlen(expr)) { + sig->expr = ecs_os_strdup(expr); + } else { + sig->expr = ecs_os_strdup("0"); + } + + ecs_parse_expr(world, name, sig->expr, ecs_parse_signature_action, sig); +} + +void ecs_sig_deinit( + ecs_sig_t *sig) +{ + ecs_vector_each(sig->columns, ecs_sig_column_t, column, { + if (column->oper_kind == EcsOperOr) { + ecs_vector_free(column->is.type); + } + ecs_os_free(column->name); + }); + + ecs_vector_free(sig->columns); + ecs_os_free(sig->expr); +} + +int ecs_sig_add( + ecs_world_t *world, + ecs_sig_t *sig, + ecs_sig_from_kind_t from_kind, + ecs_sig_oper_kind_t oper_kind, + ecs_sig_inout_kind_t inout_kind, + ecs_entity_t component, + ecs_entity_t source, + const char *arg_name) +{ + ecs_sig_column_t *elem; + + /* If component has AND role, all components of specified type must match */ + if (ECS_HAS_ROLE(component, AND)) { + elem = ecs_vector_add(&sig->columns, ecs_sig_column_t); + component &= ECS_ENTITY_MASK; + const EcsType *type = ecs_get(world, component, EcsType); + if (!type) { + ecs_parser_error(sig->name, sig->expr, 0, + "AND flag can only be applied to types"); + } + + elem->is.component = component; + elem->from_kind = from_kind; + elem->oper_kind = EcsOperAll; + elem->inout_kind = inout_kind; + elem->source = source; + + } else + + /* If component has OR role, add type as OR column */ + if (ECS_HAS_ROLE(component, OR)) { + elem = ecs_vector_add(&sig->columns, ecs_sig_column_t); + component &= ECS_ENTITY_MASK; + const EcsType *type = ecs_get(world, component, EcsType); + if (!type) { + ecs_parser_error(sig->name, sig->expr, 0, + "OR flag can only be applied to types"); + } + + elem->is.type = ecs_vector_copy(type->normalized, ecs_entity_t); + elem->from_kind = from_kind; + elem->oper_kind = EcsOperOr; + elem->inout_kind = inout_kind; + elem->source = source; + } else + + /* AND (default) and optional columns are stored the same way */ + if (oper_kind != EcsOperOr) { + elem = ecs_vector_add(&sig->columns, ecs_sig_column_t); + elem->from_kind = from_kind; + elem->oper_kind = oper_kind; + elem->inout_kind = inout_kind; + elem->is.component = component; + elem->source = source; + + /* OR columns store a type id instead of a single component */ + } else { + ecs_assert(inout_kind != EcsOut, ECS_INVALID_SIGNATURE, NULL); + elem = ecs_vector_last(sig->columns, ecs_sig_column_t); + + if (elem->from_kind != from_kind) { + /* Cannot mix FromEntity and FromComponent in OR */ + ecs_parser_error(sig->name, sig->expr, 0, + "cannot mix source kinds in || expression"); + goto error; + } + + if (elem->oper_kind != EcsOperAnd && elem->oper_kind != EcsOperOr) { + ecs_parser_error(sig->name, sig->expr, 0, + "cannot mix operators in || expression"); + goto error; + } + + if (elem->oper_kind == EcsOperAnd) { + ecs_entity_t prev = elem->is.component; + elem->is.type = NULL; + vec_add_entity(&elem->is.type, prev); + vec_add_entity(&elem->is.type, component); + } else { + vec_add_entity(&elem->is.type, component); + } + + elem->from_kind = from_kind; + elem->oper_kind = oper_kind; + } + + if (arg_name) { + elem->name = ecs_os_strdup(arg_name); + } else { + elem->name = NULL; + } + + return 0; +error: + return -1; +} + +/* Check if system meets constraints of non-table columns */ +bool ecs_sig_check_constraints( + ecs_world_t *world, + ecs_sig_t *sig) +{ + ecs_vector_each(sig->columns, ecs_sig_column_t, elem, { + ecs_sig_from_kind_t from_kind = elem->from_kind; + ecs_sig_oper_kind_t oper_kind = elem->oper_kind; + + if (from_kind == EcsFromEntity) { + ecs_type_t type = ecs_get_type(world, elem->source); + if (ecs_type_has_entity(world, type, elem->is.component)) { + if (oper_kind == EcsOperNot) { + return false; + } + } else { + if (oper_kind != EcsOperNot) { + return false; + } + } + } + }); + + return true; +} + +ecs_entity_t ecs_find_entity_in_prefabs( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type, + ecs_entity_t component, + ecs_entity_t previous) +{ + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + /* Walk from back to front, as prefabs are always located + * at the end of the type. */ + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = array[i]; + + if (ECS_HAS_ROLE(e, INSTANCEOF)) { + ecs_entity_t prefab = e & ECS_COMPONENT_MASK; + ecs_type_t prefab_type = ecs_get_type(world, prefab); + + if (prefab == previous) { + continue; + } + + if (ecs_type_owns_entity( + world, prefab_type, component, true)) + { + return prefab; + } else { + prefab = ecs_find_entity_in_prefabs( + world, prefab, prefab_type, component, entity); + if (prefab) { + return prefab; + } + } + } else { + /* If this is not a prefab, the following entities won't + * be prefabs either because the array is sorted, and + * the prefab bit is 2^63 which ensures that prefabs are + * guaranteed to be the last entities in the type */ + break; + } + } + + return 0; +} + +/* -- Private functions -- */ + +/* O(n) algorithm to check whether type 1 is equal or superset of type 2 */ +ecs_entity_t ecs_type_contains( + ecs_world_t *world, + ecs_type_t type_1, + ecs_type_t type_2, + bool match_all, + bool match_prefab) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_get_stage(&world); + + if (!type_1) { + return 0; + } + + ecs_assert(type_2 != NULL, ECS_INTERNAL_ERROR, NULL); + + if (type_1 == type_2) { + return *(ecs_vector_first(type_1, ecs_entity_t)); + } + + int32_t i_2, i_1 = 0; + ecs_entity_t e1 = 0; + ecs_entity_t *t1_array = ecs_vector_first(type_1, ecs_entity_t); + ecs_entity_t *t2_array = ecs_vector_first(type_2, ecs_entity_t); + + ecs_assert(t1_array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t2_array != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t t1_count = ecs_vector_count(type_1); + int32_t t2_count = ecs_vector_count(type_2); + + for (i_2 = 0; i_2 < t2_count; i_2 ++) { + ecs_entity_t e2 = t2_array[i_2]; + + if (i_1 >= t1_count) { + return 0; + } + + e1 = t1_array[i_1]; + + if (e2 > e1) { + do { + i_1 ++; + if (i_1 >= t1_count) { + return 0; + } + e1 = t1_array[i_1]; + } while (e2 > e1); + } + + if (e1 != e2) { + if (match_prefab && e2 != + ecs_typeid(EcsName) && e2 != + EcsPrefab && e2 != + EcsDisabled) + { + if (ecs_find_entity_in_prefabs(world, 0, type_1, e2, 0)) { + e1 = e2; + } + } + + if (e1 != e2) { + if (match_all) return 0; + } else if (!match_all) { + return e1; + } + } else { + if (!match_all) return e1; + i_1 ++; + if (i_1 < t1_count) { + e1 = t1_array[i_1]; + } + } + } + + if (match_all) { + return e1; + } else { + return 0; + } +} + +/* -- Public API -- */ + +int32_t ecs_type_index_of( + ecs_type_t type, + ecs_entity_t entity) +{ + ecs_vector_each(type, ecs_entity_t, c_ptr, { + if (*c_ptr == entity) { + return c_ptr_i; + } + }); + + return -1; +} + +ecs_type_t ecs_type_merge( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t to_add, + ecs_type_t to_remove) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_get_stage(&world); + + ecs_table_t *table = ecs_table_from_type(world, type); + ecs_entities_t add_array = ecs_type_to_entities(to_add); + ecs_entities_t remove_array = ecs_type_to_entities(to_remove); + + table = ecs_table_traverse_remove( + world, table, &remove_array, NULL); + + table = ecs_table_traverse_add( + world, table, &add_array, NULL); + + if (!table) { + return NULL; + } else { + return table->type; + } +} + +ecs_type_t ecs_type_find( + ecs_world_t *world, + ecs_entity_t *array, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_get_stage(&world); + + ecs_entities_t entities = { + .array = array, + .count = count + }; + + ecs_table_t *table = ecs_table_find_or_create(world, &entities); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +static +bool has_trait( + ecs_entity_t trait, + ecs_entity_t e) +{ + return trait == ecs_entity_t_hi(e & ECS_COMPONENT_MASK); +} + +static +bool has_case( + ecs_world_t *world, + ecs_entity_t sw_case, + ecs_entity_t e) +{ + const EcsType *type_ptr = ecs_get(world, e & ECS_COMPONENT_MASK, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_type_has_entity(world, type_ptr->normalized, sw_case); +} + +static +int match_entity( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t e, + ecs_entity_t match_with) +{ + if (ECS_HAS_ROLE(match_with, TRAIT)) { + ecs_entity_t hi = ecs_entity_t_hi(match_with & ECS_COMPONENT_MASK); + ecs_entity_t lo = ecs_entity_t_lo(match_with); + + if (lo == EcsWildcard) { + ecs_assert(hi != 0, ECS_INTERNAL_ERROR, NULL); + + if (!ECS_HAS_ROLE(e, TRAIT) || !has_trait(hi, e)) { + return 0; + } + + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + + ecs_entity_t comp = ecs_entity_t_lo(e); + for (i = 0; i < count; i ++) { + if (comp == ids[i]) { + return 2; + } + } + + return -1; + } else if (!hi) { + if (ECS_HAS_ROLE(e, TRAIT) && has_trait(lo, e)) { + return 1; + } + } + } else + if (ECS_HAS_ROLE(match_with, CASE)) { + ecs_entity_t sw_case = match_with & ECS_COMPONENT_MASK; + if (ECS_HAS_ROLE(e, SWITCH) && has_case(world, sw_case, e)) { + return 1; + } else { + return 0; + } + } + + if (e == match_with) { + return 1; + } + + return 0; +} + +static +bool search_type( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity, + bool owned) +{ + if (!type) { + return false; + } + + if (!entity) { + return true; + } + + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + int matched = 0; + + for (i = 0; i < count; i ++) { + int ret = match_entity(world, type, ids[i], entity); + switch(ret) { + case 0: break; + case 1: return true; + case -1: return false; + case 2: matched ++; break; + default: ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + } + + if (!matched && !owned && entity != EcsPrefab && entity != EcsDisabled) { + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = ids[i]; + if (!ECS_HAS_ROLE(e, INSTANCEOF)) { + break; + } + + ecs_entity_t base = e & ECS_COMPONENT_MASK; + ecs_type_t base_type = ecs_get_type(world, base); + + if (search_type(world, base_type, entity, false)) { + return true; + } + } + } + + return matched != 0; +} + +bool ecs_type_has_entity( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity) +{ + return search_type(world, type, entity, false); +} + +bool ecs_type_owns_entity( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity, + bool owned) +{ + return search_type(world, type, entity, owned); +} + +bool ecs_type_has_type( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t has) +{ + return ecs_type_contains(world, type, has, true, false) != 0; +} + +bool ecs_type_owns_type( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t has, + bool owned) +{ + return ecs_type_contains(world, type, has, true, !owned) != 0; +} + +ecs_type_t ecs_type_add( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t e) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_get_stage(&world); + ecs_table_t *table = ecs_table_from_type(world, type); + + ecs_entities_t entities = { + .array = &e, + .count = 1 + }; + + table = ecs_table_traverse_add(world, table, &entities, NULL); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +ecs_type_t ecs_type_remove( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t e) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_get_stage(&world); + ecs_table_t *table = ecs_table_from_type(world, type); + + ecs_entities_t entities = { + .array = &e, + .count = 1 + }; + + table = ecs_table_traverse_remove(world, table, &entities, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +char* ecs_type_str( + ecs_world_t *world, + ecs_type_t type) +{ + if (!type) { + return ecs_os_strdup(""); + } + + ecs_vector_t *chbuf = ecs_vector_new(char, 32); + char *dst; + + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + char buffer[256]; + ecs_size_t len; + + if (i) { + *(char*)ecs_vector_add(&chbuf, char) = ','; + } + + if (e == 1) { + ecs_os_strcpy(buffer, "EcsComponent"); + len = ecs_os_strlen("EcsComponent"); + } else { + len = ecs_from_size_t(ecs_entity_str(world, e, buffer, 256)); + } + + dst = ecs_vector_addn(&chbuf, char, len); + ecs_os_memcpy(dst, buffer, len); + } + + *(char*)ecs_vector_add(&chbuf, char) = '\0'; + + char* result = ecs_os_strdup(ecs_vector_first(chbuf, char)); + ecs_vector_free(chbuf); + return result; +} + +ecs_entity_t ecs_type_get_entity_for_xor( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t xor) +{ + ecs_assert( + ecs_type_owns_entity(world, type, ECS_XOR | xor, true), + ECS_INVALID_PARAMETER, NULL); + + const EcsType *type_ptr = ecs_get(world, xor, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t xor_type = type_ptr->normalized; + ecs_assert(xor_type != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + for (i = 0; i < count; i ++) { + if (ecs_type_owns_entity(world, xor_type, array[i], true)) { + return array[i]; + } + } + + return 0; +} + +int32_t ecs_type_trait_index_of( + ecs_type_t type, + int32_t start_index, + ecs_entity_t trait) +{ + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + for (i = start_index; i < count; i ++) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, TRAIT)) { + e &= ECS_COMPONENT_MASK; + if (trait == ecs_entity_t_hi(e)) { + return i; + } + } + } + + return -1; +} + +void ecs_os_api_impl(ecs_os_api_t *api); + +static bool ecs_os_api_initialized = false; +static int ecs_os_api_init_count = 0; + +ecs_os_api_t ecs_os_api; + +int64_t ecs_os_api_malloc_count = 0; +int64_t ecs_os_api_realloc_count = 0; +int64_t ecs_os_api_calloc_count = 0; +int64_t ecs_os_api_free_count = 0; + +void ecs_os_set_api( + ecs_os_api_t *os_api) +{ + if (!ecs_os_api_initialized) { + ecs_os_api = *os_api; + ecs_os_api_initialized = true; + } +} + +void ecs_os_init(void) +{ + if (!ecs_os_api_initialized) { + ecs_os_set_api_defaults(); + } + + if (!(ecs_os_api_init_count ++)) { + if (ecs_os_api.init_) { + ecs_os_api.init_(); + } + } +} + +void ecs_os_fini(void) { + if (!--ecs_os_api_init_count) { + if (ecs_os_api.fini_) { + ecs_os_api.fini_(); + } + } +} + +static +void ecs_log(const char *fmt, va_list args) { + vfprintf(stdout, fmt, args); + fprintf(stdout, "\n"); +} + +static +void ecs_log_error(const char *fmt, va_list args) { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +static +void ecs_log_debug(const char *fmt, va_list args) { + vfprintf(stdout, fmt, args); + fprintf(stdout, "\n"); +} + +static +void ecs_log_warning(const char *fmt, va_list args) { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +void ecs_os_dbg(const char *fmt, ...) { +#ifndef NDEBUG + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_debug_) { + ecs_os_api.log_debug_(fmt, args); + } + va_end(args); +#else + (void)fmt; +#endif +} + +void ecs_os_warn(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_warning_) { + ecs_os_api.log_warning_(fmt, args); + } + va_end(args); +} + +void ecs_os_log(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_) { + ecs_os_api.log_(fmt, args); + } + va_end(args); +} + +void ecs_os_err(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_error_) { + ecs_os_api.log_error_(fmt, args); + } + va_end(args); +} + +static +void ecs_os_gettime(ecs_time_t *time) +{ + uint64_t now = ecs_os_time_now(); + uint64_t sec = now / 1000000000; + + assert(sec < UINT32_MAX); + assert((now - sec * 1000000000) < UINT32_MAX); + + time->sec = (uint32_t)sec; + time->nanosec = (uint32_t)(now - sec * 1000000000); +} + +static +void* ecs_os_api_malloc(ecs_size_t size) { + ecs_os_api_malloc_count ++; + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + return malloc((size_t)size); +} + +static +void* ecs_os_api_calloc(ecs_size_t size) { + ecs_os_api_calloc_count ++; + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + return calloc(1, (size_t)size); +} + +static +void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + + if (ptr) { + ecs_os_api_realloc_count ++; + } else { + /* If not actually reallocing, treat as malloc */ + ecs_os_api_malloc_count ++; + } + + return realloc(ptr, (size_t)size); +} + +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + ecs_os_api_free_count ++; + } + free(ptr); +} + +static +char* ecs_os_api_strdup(const char *str) { + int len = ecs_os_strlen(str); + char *result = ecs_os_malloc(len + 1); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_strcpy(result, str); + return result; +} + +/* Replace dots with underscores */ +static +char *module_file_base(const char *module, char sep) { + char *base = ecs_os_strdup(module); + ecs_size_t i, len = ecs_os_strlen(base); + for (i = 0; i < len; i ++) { + if (base[i] == '.') { + base[i] = sep; + } + } + + return base; +} + +static +char* ecs_os_api_module_to_dl(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; + + /* Best guess, use module name with underscores + OS library extension */ + char *file_base = module_file_base(module, '_'); + +#if defined(ECS_OS_LINUX) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".so"); +#elif defined(ECS_OS_DARWIN) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dylib"); +#elif defined(ECS_OS_WINDOWS) + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dll"); +#endif + + ecs_os_free(file_base); + + return ecs_strbuf_get(&lib); +} + +static +char* ecs_os_api_module_to_etc(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; + + /* Best guess, use module name with dashes + /etc */ + char *file_base = module_file_base(module, '-'); + + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, "/etc"); + + ecs_os_free(file_base); + + return ecs_strbuf_get(&lib); +} + +void ecs_os_set_api_defaults(void) +{ + /* Don't overwrite if already initialized */ + if (ecs_os_api_initialized != 0) { + return; + } + + ecs_os_time_setup(); + + /* Memory management */ + ecs_os_api.malloc_ = ecs_os_api_malloc; + ecs_os_api.free_ = ecs_os_api_free; + ecs_os_api.realloc_ = ecs_os_api_realloc; + ecs_os_api.calloc_ = ecs_os_api_calloc; + + /* Strings */ + ecs_os_api.strdup_ = ecs_os_api_strdup; + + /* Time */ + ecs_os_api.sleep_ = ecs_os_time_sleep; + ecs_os_api.get_time_ = ecs_os_gettime; + + /* Logging */ + ecs_os_api.log_ = ecs_log; + ecs_os_api.log_error_ = ecs_log_error; + ecs_os_api.log_debug_ = ecs_log_debug; + ecs_os_api.log_warning_ = ecs_log_warning; + + /* Modules */ + if (!ecs_os_api.module_to_dl_) { + ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; + } + + if (!ecs_os_api.module_to_etc_) { + ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; + } + + ecs_os_api.abort_ = abort; +} + +bool ecs_os_has_heap(void) { + return + (ecs_os_api.malloc_ != NULL) && + (ecs_os_api.calloc_ != NULL) && + (ecs_os_api.realloc_ != NULL) && + (ecs_os_api.free_ != NULL); +} + +bool ecs_os_has_threading(void) { + return + (ecs_os_api.mutex_new_ != NULL) && + (ecs_os_api.mutex_free_ != NULL) && + (ecs_os_api.mutex_lock_ != NULL) && + (ecs_os_api.mutex_unlock_ != NULL) && + (ecs_os_api.cond_new_ != NULL) && + (ecs_os_api.cond_free_ != NULL) && + (ecs_os_api.cond_wait_ != NULL) && + (ecs_os_api.cond_signal_ != NULL) && + (ecs_os_api.cond_broadcast_ != NULL) && + (ecs_os_api.thread_new_ != NULL) && + (ecs_os_api.thread_join_ != NULL); +} + +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL); +} + +bool ecs_os_has_logging(void) { + return + (ecs_os_api.log_ != NULL) && + (ecs_os_api.log_error_ != NULL) && + (ecs_os_api.log_debug_ != NULL) && + (ecs_os_api.log_warning_ != NULL); +} + +bool ecs_os_has_dl(void) { + return + (ecs_os_api.dlopen_ != NULL) && + (ecs_os_api.dlproc_ != NULL) && + (ecs_os_api.dlclose_ != NULL); +} + +bool ecs_os_has_modules(void) { + return + (ecs_os_api.module_to_dl_ != NULL) && + (ecs_os_api.module_to_etc_ != NULL); +} + +#ifdef FLECS_SYSTEMS_H +#endif + +static +void activate_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table, + bool active); + +static +ecs_entity_t components_contains( + ecs_world_t *world, + ecs_type_t table_type, + ecs_type_t type, + ecs_entity_t *entity_out, + bool match_all) +{ + ecs_vector_each(table_type, ecs_entity_t, c_ptr, { + ecs_entity_t entity = *c_ptr; + + if (ECS_HAS_ROLE(entity, CHILDOF)) { + entity &= ECS_COMPONENT_MASK; + + ecs_record_t *record = ecs_eis_get(world, entity); + ecs_assert(record != 0, ECS_INTERNAL_ERROR, NULL); + + if (record->table) { + ecs_entity_t component = ecs_type_contains( + world, record->table->type, type, match_all, true); + + if (component) { + if (entity_out) *entity_out = entity; + return component; + } + } + } + }); + + return 0; +} + +/* Get actual entity on which specified component is stored */ +static +ecs_entity_t get_entity_for_component( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type, + ecs_entity_t component) +{ + if (entity) { + ecs_record_t *record = ecs_eis_get(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + if (record->table) { + type = record->table->type; + } else { + type = NULL; + } + } + + ecs_vector_each(type, ecs_entity_t, c_ptr, { + if (*c_ptr == component) { + return entity; + } + }); + + return ecs_find_entity_in_prefabs(world, entity, type, component, 0); +} + +#ifndef NDEBUG +static +ecs_entity_t get_cascade_component( + ecs_query_t *query) +{ + ecs_sig_column_t *column = ecs_vector_first(query->sig.columns, ecs_sig_column_t); + return column[query->cascade_by - 1].is.component; +} +#endif + +static +int32_t rank_by_depth( + ecs_world_t *world, + ecs_entity_t rank_by_component, + ecs_type_t type) +{ + int32_t result = 0; + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + for (i = count - 1; i >= 0; i --) { + if (ECS_HAS_ROLE(array[i], CHILDOF)) { + ecs_type_t c_type = ecs_get_type(world, array[i] & ECS_COMPONENT_MASK); + int32_t j, c_count = ecs_vector_count(c_type); + ecs_entity_t *c_array = ecs_vector_first(c_type, ecs_entity_t); + + for (j = 0; j < c_count; j ++) { + if (c_array[j] == rank_by_component) { + result ++; + result += rank_by_depth(world, rank_by_component, c_type); + break; + } + } + + if (j != c_count) { + break; + } + } else if (!(array[i] & ECS_ROLE_MASK)) { + /* No more parents after this */ + break; + } + } + + return result; +} + +static +int table_compare( + const void *t1, + const void *t2) +{ + const ecs_matched_table_t *table_1 = t1; + const ecs_matched_table_t *table_2 = t2; + + return table_1->rank - table_2->rank; +} + +static +bool has_auto_activation( + ecs_query_t *q) +{ + /* Only a basic query with no additional features does table activation */ + return !(q->flags & EcsQueryNoActivation); +} + +static +void order_ranked_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + if (query->group_table) { + ecs_vector_sort(query->tables, ecs_matched_table_t, table_compare); + + /* Recompute the table indices by first resetting all indices, and then + * re-adding them one by one. */ + if (has_auto_activation(query)) { + ecs_map_iter_t it = ecs_map_iter(query->table_indices); + ecs_table_indices_t *ti; + while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) { + /* If table is registered, it must have at least one index */ + int32_t count = ti->count; + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + (void)count; + + /* Only active tables are reordered, so don't reset inactive + * tables */ + if (ti->indices[0] >= 0) { + ti->count = 0; + } + } + } + + /* Re-register monitors after tables have been reordered. This will update + * the table administration with the new matched_table ids, so that when a + * monitor is executed we can quickly find the right matched_table. */ + if (query->flags & EcsQueryMonitor) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + ecs_table_notify(world, table->iter_data.table, &(ecs_table_event_t){ + .kind = EcsTableQueryMatch, + .query = query, + .matched_table_index = table_i + }); + }); + } + + /* Update table index */ + if (has_auto_activation(query)) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + ecs_table_indices_t *ti = ecs_map_get(query->table_indices, + ecs_table_indices_t, table->iter_data.table->id); + + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ti->indices[ti->count] = table_i; + ti->count ++; + }); + } + } + + query->match_count ++; + query->needs_reorder = false; +} + +static +void group_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_matched_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (query->group_table) { + ecs_assert(table->iter_data.table != NULL, ECS_INTERNAL_ERROR, NULL); + table->rank = query->group_table( + world, query->rank_on_component, table->iter_data.table->type); + } else { + table->rank = 0; + } +} + +/* Rank all tables of query. Only necessary if a new ranking function was + * provided or if a monitored entity set the component used for ranking. */ +static +void group_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + if (query->group_table) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + group_table(world, query, table); + }); + + ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, { + group_table(world, query, table); + }); + } +} + +#ifndef NDEBUG + +static +const char* query_name( + ecs_world_t *world, + ecs_query_t *q) +{ + if (q->system) { + return ecs_get_name(world, q->system); + } else { + return q->sig.expr; + } +} + +#endif + +static +void get_comp_and_src( + ecs_world_t *world, + ecs_query_t *query, + ecs_type_t table_type, + ecs_sig_column_t *column, + ecs_sig_oper_kind_t op, + ecs_sig_from_kind_t from, + ecs_entity_t *component_out, + ecs_entity_t *entity_out) +{ + ecs_entity_t component = 0, entity = 0; + + if (op == EcsOperNot) { + entity = column->source; + } + + /* Column that retrieves data from self or a fixed entity */ + if (from == EcsFromAny || from == EcsFromEntity || + from == EcsFromOwned || from == EcsFromShared) + { + if (op == EcsOperAnd || op == EcsOperNot || op == EcsOperOptional) { + component = column->is.component; + } else if (op == EcsOperAll) { + component = column->is.component & ECS_COMPONENT_MASK; + } else if (op == EcsOperOr) { + component = ecs_type_contains( + world, table_type, column->is.type, + false, true); + } + + if (from == EcsFromEntity) { + entity = column->source; + } + + /* Column that just passes a handle to the system (no data) */ + } else if (from == EcsFromEmpty) { + component = column->is.component; + + /* Column that retrieves data from a dynamic entity */ + } else if (from == EcsFromParent || from == EcsCascade) { + if (op == EcsOperAnd || + op == EcsOperOptional) + { + component = column->is.component; + entity = ecs_find_in_type( + world, table_type, component, ECS_CHILDOF); + + } else if (op == EcsOperOr) { + component = components_contains( + world, + table_type, + column->is.type, + &entity, + false); + } + + /* Column that retrieves data from a system */ + } else if (from == EcsFromSystem) { + if (op == EcsOperAnd) { + component = column->is.component; + } + + entity = query->system; + } + + *component_out = component; + *entity_out = entity; +} + +typedef struct trait_offset_t { + int32_t index; + int32_t count; +} trait_offset_t; + +/* Get index for specified trait. Take into account that a trait can be matched + * multiple times per table, by keeping an offset of the last found index */ +static +int32_t get_trait_index( + ecs_type_t table_type, + ecs_entity_t component, + int32_t column_index, + trait_offset_t *trait_offsets, + int32_t count) +{ + int32_t result; + + /* The count variable keeps track of the number of times a trait has been + * matched with the current table. Compare the count to check if the index + * was already resolved for this iteration */ + if (trait_offsets[column_index].count == count) { + /* If it was resolved, return the last stored index. Subtract one as the + * index is offset by one, to ensure we're not getting stuck on the same + * index. */ + result = trait_offsets[column_index].index - 1; + } else { + /* First time for this iteration that the trait index is resolved, look + * it up in the type. */ + result = ecs_type_trait_index_of(table_type, + trait_offsets[column_index].index, component); + trait_offsets[column_index].index = result + 1; + trait_offsets[column_index].count = count; + } + + return result; +} + +static +int32_t get_component_index( + ecs_world_t *world, + ecs_table_t *table, + ecs_type_t table_type, + ecs_entity_t *component_out, + int32_t column_index, + ecs_sig_oper_kind_t op, + trait_offset_t *trait_offsets, + int32_t count) +{ + int32_t result = 0; + ecs_entity_t component = *component_out; + + if (component) { + /* If requested component is a case, find the corresponding switch to + * lookup in the table */ + if (ECS_HAS_ROLE(component, CASE)) { + result = ecs_table_switch_from_case( + world, table, component); + ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL); + + result += table->sw_column_offset; + } else + if (ECS_HAS_ROLE(component, TRAIT)) { + /* If only the lo part of the trait identifier is set, interpret it + * as the trait to match. This will match any instance of the trait + * on the entity and in a signature looks like "TRAIT | MyTrait". */ + if (!ecs_entity_t_hi(component & ECS_COMPONENT_MASK)) { + ecs_assert(trait_offsets != NULL, + ECS_INTERNAL_ERROR, NULL); + + /* Strip the TRAIT role */ + component &= ECS_COMPONENT_MASK; + + /* Get index of trait. Start looking from the last trait index + * as this may not be the first instance of the trait. */ + result = get_trait_index( + table_type, component, column_index, trait_offsets, count); + + if (result != -1) { + /* If component of current column is a trait, get the actual + * trait type for the table, so the system can see which + * component the trait was applied to */ + ecs_entity_t *trait = ecs_vector_get( + table_type, ecs_entity_t, result); + *component_out = *trait; + + /* Check if the trait is a tag or whether it has data */ + if (ecs_get(world, component, EcsComponent) == NULL) { + /* If trait has no data associated with it, use the + * component to which the trait has been added */ + component = ecs_entity_t_lo(*trait); + } + } + } else { + /* If trait does have the hi part of the identifier set, this is + * a fully qualified trait identifier. In a signature this looks + * like "TRAIT | MyTrait > Comp". */ + ecs_entity_t lo = ecs_entity_t_lo(component); + if (lo == EcsWildcard) { + ecs_assert(trait_offsets != NULL, + ECS_INTERNAL_ERROR, NULL); + + /* Get id for the trait to lookup by taking the trait from + * the high 32 bits, move it to the low 32 bits, and reapply + * the TRAIT mask. */ + component = ecs_entity_t_hi(component & ECS_COMPONENT_MASK); + + /* If the low part of the identifier is the wildcard entity, + * this column is requesting the component to which the + * trait is applied. First, find the component identifier */ + result = get_trait_index(table_type, component, + column_index, trait_offsets, count); + + /* Type must have the trait, otherwise table would not have + * matched */ + ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL); + + /* Get component id at returned index */ + ecs_entity_t *trait = ecs_vector_get( + table_type, ecs_entity_t, result); + ecs_assert(trait != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get the lower part of the trait id. This is the component + * we're looking for. */ + component = ecs_entity_t_lo(*trait); + *component_out = component; + + /* Now lookup the component as usual */ + } + + /* If the low part is a regular entity (component), then + * this query exactly matches a single trait instance. In + * this case we can simply do a lookup of the trait + * identifier in the table type. */ + result = ecs_type_index_of(table_type, component); + } + } else { + /* Get column index for component */ + result = ecs_type_index_of(table_type, component); + } + + /* If column is found, add one to the index, as column zero in + * a table is reserved for entity id's */ + if (result != -1) { + result ++; + } + + /* Check if component is a tag. If it is, set table_data to + * zero, so that a system won't try to access the data */ + if (!ECS_HAS_ROLE(component, CASE) && + !ECS_HAS_ROLE(component, SWITCH)) + { + component = ecs_get_typeid(world, component); + const EcsComponent *data = ecs_get( + world, component, EcsComponent); + + if (!data || !data->size) { + result = 0; + } + } + + /* ecs_table_column_offset may return -1 if the component comes + * from a prefab. If so, the component will be resolved as a + * reference (see below) */ + } + + if (op == EcsOperAll) { + result = 0; + } else if (op == EcsOperOptional) { + /* If table doesn't have the field, mark it as no data */ + if (!ecs_type_has_entity(world, table_type, component)) { + result = 0; + } + } + + return result; +} + +static +ecs_vector_t* add_ref( + ecs_world_t *world, + ecs_query_t *query, + ecs_type_t table_type, + ecs_vector_t *references, + ecs_entity_t component, + ecs_entity_t entity, + ecs_sig_from_kind_t from) +{ + const EcsComponent *c_info = ecs_get(world, component, EcsComponent); + + ecs_entity_t e; + ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); + + /* Find the entity for the component */ + if (from == EcsFromEntity || from == EcsFromEmpty) { + e = entity; + } else if (from == EcsCascade) { + e = entity; + } else if (from == EcsFromSystem) { + e = entity; + } else { + e = get_entity_for_component( + world, entity, table_type, component); + } + + if (from != EcsCascade) { + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + } + + *ref = (ecs_ref_t){0}; + ref->entity = e; + ref->component = component; + + if (ecs_has(world, component, EcsComponent)) { + if (c_info->size && from != EcsFromEmpty) { + if (e) { + ecs_get_ref_w_entity( + world, ref, e, component); + ecs_set_watch(world, e); + } + + query->flags |= EcsQueryHasRefs; + } + } + + return references; +} + +static +ecs_entity_t is_column_trait( + ecs_sig_column_t *column) +{ + ecs_sig_from_kind_t from_kind = column->from_kind; + ecs_sig_oper_kind_t oper_kind = column->oper_kind; + + /* For now traits are only supported on owned columns */ + if (from_kind == EcsFromOwned && oper_kind == EcsOperAnd) { + ecs_entity_t c = column->is.component; + if (ECS_HAS_ROLE(c, TRAIT)) { + if (!(ecs_entity_t_hi(c & ECS_COMPONENT_MASK))) { + return c; + } else + if (ecs_entity_t_lo(c) == EcsWildcard) { + return ecs_entity_t_hi(c & ECS_COMPONENT_MASK); + } + } + } + + return 0; +} + +static +int32_t type_trait_count( + ecs_type_t type, + ecs_entity_t trait) +{ + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t result = 0; + + trait &= ECS_COMPONENT_MASK; + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + if (ECS_HAS_ROLE(e, TRAIT)) { + e &= ECS_COMPONENT_MASK; + if (ecs_entity_t_hi(e) == trait) { + result ++; + } + } + } + + return result; +} + +/* For each trait that the query subscribes for, count the occurrences in the + * table. Cardinality of subscribed for traits must be the same as in the table + * or else the table won't match. */ +static +int32_t count_traits( + ecs_query_t *query, + ecs_type_t type) +{ + ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t); + int32_t i, count = ecs_vector_count(query->sig.columns); + int32_t first_count = 0, trait_count = 0; + + for (i = 0; i < count; i ++) { + ecs_entity_t trait = is_column_trait(&columns[i]); + if (trait) { + trait_count = type_trait_count(type, trait); + if (!first_count) { + first_count = trait_count; + } else { + if (first_count != trait_count) { + /* The traits that this query subscribed for occur in the + * table but don't have the same cardinality. Ignore the + * table. This could typically happen for empty tables along + * a path in the table graph. */ + return -1; + } + } + } + } + + return first_count; +} + +static +ecs_type_t get_column_type( + ecs_world_t *world, + ecs_sig_oper_kind_t oper_kind, + ecs_entity_t component) +{ + if (oper_kind == EcsOperAll) { + const EcsType *type = ecs_get(world, component, EcsType); + ecs_assert(type != NULL, ECS_INVALID_PARAMETER, NULL); + return type->normalized; + } else { + return ecs_type_from_entity(world, component); + } +} + +/** Add table to system, compute offsets for system components in table it */ +static +void add_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + ecs_type_t table_type = NULL; + int32_t c, column_count = ecs_vector_count(query->sig.columns); + + if (table) { + table_type = table->type; + } + + int32_t trait_cur = 0, trait_count = count_traits(query, table_type); + + /* If the query has traits, we need to account for the fact that a table may + * have multiple components to which the trait is applied, which means the + * table has to be registered with the query multiple times, with different + * table columns. If so, allocate a small array for each trait in which the + * last added table index of the trait is stored, so that in the next + * iteration we can start the search from the correct offset type. */ + trait_offset_t *trait_offsets = NULL; + if (trait_count) { + trait_offsets = ecs_os_calloc( + ECS_SIZEOF(trait_offset_t) * column_count); + } + + /* From here we recurse */ + int32_t *table_indices = NULL; + int32_t table_indices_count = 0; + int32_t matched_table_index = 0; + ecs_matched_table_t table_data; + ecs_vector_t *references = NULL; + +add_trait: + table_data = (ecs_matched_table_t){ .iter_data.table = table }; + if (table) { + table_type = table->type; + } + + /* If grouping is enabled for query, assign the group rank to the table */ + group_table(world, query, &table_data); + + if (column_count) { + /* Array that contains the system column to table column mapping */ + table_data.iter_data.columns = ecs_os_malloc(ECS_SIZEOF(int32_t) * column_count); + ecs_assert(table_data.iter_data.columns != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Store the components of the matched table. In the case of OR expressions, + * components may differ per matched table. */ + table_data.iter_data.components = ecs_os_malloc(ECS_SIZEOF(ecs_entity_t) * column_count); + ecs_assert(table_data.iter_data.components != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Also cache types, so no lookup is needed while iterating */ + table_data.iter_data.types = ecs_os_malloc(ECS_SIZEOF(ecs_type_t) * column_count); + ecs_assert(table_data.iter_data.types != NULL, ECS_OUT_OF_MEMORY, NULL); + } + + /* Walk columns parsed from the system signature */ + ecs_sig_column_t *columns = ecs_vector_first( + query->sig.columns, ecs_sig_column_t); + + for (c = 0; c < column_count; c ++) { + ecs_sig_column_t *column = &columns[c]; + ecs_entity_t entity = 0, component = 0; + ecs_sig_oper_kind_t op = column->oper_kind; + ecs_sig_from_kind_t from = column->from_kind; + + if (op == EcsOperNot) { + from = EcsFromEmpty; + } + + table_data.iter_data.columns[c] = 0; + + /* Get actual component and component source for current column */ + get_comp_and_src(world, query, table_type, column, op, from, &component, + &entity); + + /* This column does not retrieve data from a static entity (either + * EcsFromSystem or EcsFromParent) and is not just a handle */ + if (!entity && from != EcsFromEmpty) { + int32_t index = get_component_index(world, table, table_type, + &component, c, op, trait_offsets, trait_cur + 1); + + if (index == -1) { + if (from == EcsFromOwned && op == EcsOperOptional) { + index = 0; + } + } else { + if (from == EcsFromShared && op == EcsOperOptional) { + index = 0; + } + } + + table_data.iter_data.columns[c] = index; + + /* If the column is a case, we should only iterate the entities in + * the column for this specific case. Add a sparse column with the + * case id so we can find the correct entities when iterating */ + if (ECS_HAS_ROLE(component, CASE)) { + ecs_sparse_column_t *sc = ecs_vector_add( + &table_data.sparse_columns, ecs_sparse_column_t); + sc->signature_column_index = c; + sc->sw_case = component & ECS_COMPONENT_MASK; + sc->sw_column = NULL; + } + + /* If table has a disabled bitmask for components, check if there is + * a disabled column for the queried for component. If so, cache it + * in a vector as the iterator will need to skip the entity when the + * component is disabled. */ + if (index && (table->flags & EcsTableHasDisabled)) { + ecs_entity_t bs_id = + (component & ECS_COMPONENT_MASK) | ECS_DISABLED; + int32_t bs_index = ecs_type_index_of(table->type, bs_id); + if (bs_index != -1) { + ecs_bitset_column_t *elem = ecs_vector_add( + &table_data.bitset_columns, ecs_bitset_column_t); + elem->column_index = bs_index; + elem->bs_column = NULL; + } + } + } + + /* Check if a the component is a reference. If 'entity' is set, the + * component must be resolved from another entity, which is the case + * for FromEntity and FromContainer. + * + * If no entity is set but the component is not found in the table, it + * must come from a prefab. This is guaranteed, as at this point it is + * already validated that the table matches with the system. + * + * If the column from is Cascade, there may not be an entity in case the + * current table contains root entities. In that case, still add a + * reference field. The application can, after the table has matched, + * change the set of components, so that this column will turn into a + * reference. Having the reference already linked to the system table + * makes changing this administation easier when the change happens. + */ + if ((entity || table_data.iter_data.columns[c] == -1 || from == EcsCascade)) { + references = add_ref(world, query, table_type, references, + component, entity, from); + table_data.iter_data.columns[c] = -ecs_vector_count(references); + } + + table_data.iter_data.components[c] = component; + table_data.iter_data.types[c] = get_column_type(world, op, component); + } + + /* Initially always add table to inactive group. If the system is registered + * with the table and the table is not empty, the table will send an + * activate signal to the system. */ + + ecs_matched_table_t *table_elem; + if (table && has_auto_activation(query)) { + table_elem = ecs_vector_add(&query->empty_tables, + ecs_matched_table_t); + + /* Store table index */ + matched_table_index = ecs_vector_count(query->empty_tables); + table_indices_count ++; + table_indices = ecs_os_realloc( + table_indices, table_indices_count * ECS_SIZEOF(int32_t)); + table_indices[table_indices_count - 1] = -matched_table_index; + + #ifndef NDEBUG + char *type_expr = ecs_type_str(world, table->type); + ecs_trace_2("query #[green]%s#[reset] matched with table #[green][%s]", + query_name(world, query), type_expr); + ecs_os_free(type_expr); + #endif + } else { + /* If no table is provided to function, this is a system that contains + * no columns that require table matching. In this case, the system will + * only have one "dummy" table that caches data from the system columns. + * Always add this dummy table to the list of active tables, since it + * would never get activated otherwise. */ + table_elem = ecs_vector_add(&query->tables, ecs_matched_table_t); + + /* If query doesn't automatically activates/inactivates tables, we can + * get the count to determine the current table index. */ + matched_table_index = ecs_vector_count(query->tables) - 1; + ecs_assert(matched_table_index >= 0, ECS_INTERNAL_ERROR, NULL); + } + + if (references) { + ecs_size_t ref_size = ECS_SIZEOF(ecs_ref_t) * ecs_vector_count(references); + table_data.iter_data.references = ecs_os_malloc(ref_size); + ecs_os_memcpy(table_data.iter_data.references, + ecs_vector_first(references, ecs_ref_t), ref_size); + ecs_vector_free(references); + references = NULL; + } + + *table_elem = table_data; + + /* Use tail recursion when adding table for multiple traits */ + trait_cur ++; + if (trait_cur < trait_count) { + goto add_trait; + } + + /* Register table indices before sending out the match signal. This signal + * can cause table activation, and table indices are needed for that. */ + if (table_indices) { + ecs_table_indices_t *ti = ecs_map_ensure( + query->table_indices, ecs_table_indices_t, table->id); + if (ti->indices) { + ecs_os_free(ti->indices); + } + ti->indices = table_indices; + ti->count = table_indices_count; + } + + if (table && !(query->flags & EcsQueryIsSubquery)) { + ecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryMatch, + .query = query, + .matched_table_index = matched_table_index + }); + } else if (table && ecs_table_count(table)) { + activate_table(world, query, table, true); + } + + if (trait_offsets) { + ecs_os_free(trait_offsets); + } +} + +static +bool match_column( + ecs_world_t *world, + ecs_type_t type, + ecs_sig_from_kind_t from_kind, + ecs_entity_t component, + ecs_entity_t source, + ecs_match_failure_t *failure_info) +{ + if (from_kind == EcsFromAny) { + failure_info->reason = EcsMatchFromSelf; + return ecs_type_has_entity(world, type, component); + + } else if (from_kind == EcsFromOwned) { + failure_info->reason = EcsMatchFromOwned; + return ecs_type_owns_entity(world, type, component, true); + + } else if (from_kind == EcsFromShared) { + failure_info->reason = EcsMatchFromShared; + return !ecs_type_owns_entity(world, type, component, true) && + ecs_type_owns_entity(world, type, component, false); + + } else if (from_kind == EcsFromParent) { + failure_info->reason = EcsMatchFromContainer; + return ecs_find_in_type(world, type, component, ECS_CHILDOF) != 0; + + } else if (from_kind == EcsFromEntity) { + failure_info->reason = EcsMatchFromEntity; + ecs_type_t source_type = ecs_get_type(world, source); + return ecs_type_has_entity(world, source_type, component); + } else { + return true; + } +} + +/* Match table with system */ +bool ecs_query_match( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + ecs_match_failure_t *failure_info) +{ + /* Prevent having to add if not null checks everywhere */ + ecs_match_failure_t tmp_failure_info; + if (!failure_info) { + failure_info = &tmp_failure_info; + } + + failure_info->reason = EcsMatchOk; + failure_info->column = 0; + + if (!(query->flags & EcsQueryNeedsTables)) { + failure_info->reason = EcsMatchSystemIsATask; + return false; + } + + ecs_type_t type, table_type = table->type; + + /* Don't match disabled entities */ + if (!(query->flags & EcsQueryMatchDisabled) && ecs_type_owns_entity( + world, table_type, EcsDisabled, true)) + { + failure_info->reason = EcsMatchEntityIsDisabled; + return false; + } + + /* Don't match prefab entities */ + if (!(query->flags & EcsQueryMatchPrefab) && ecs_type_owns_entity( + world, table_type, EcsPrefab, true)) + { + failure_info->reason = EcsMatchEntityIsPrefab; + return false; + } + + /* Check if trait cardinality matches traits in query, if any */ + if (count_traits(query, table->type) == -1) { + return false; + } + + int32_t i, column_count = ecs_vector_count(query->sig.columns); + ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t); + + for (i = 0; i < column_count; i ++) { + ecs_sig_column_t *elem = &columns[i]; + ecs_sig_from_kind_t from_kind = elem->from_kind; + ecs_sig_oper_kind_t oper_kind = elem->oper_kind; + + failure_info->column = i + 1; + + if (oper_kind == EcsOperAnd) { + if (!match_column( + world, table_type, from_kind, elem->is.component, + elem->source, failure_info)) + { + return false; + } + + } else if (oper_kind == EcsOperNot) { + if (match_column( + world, table_type, from_kind, elem->is.component, + elem->source, failure_info)) + { + return false; + } + + } else if (oper_kind == EcsOperOr || oper_kind == EcsOperAll) { + bool match_all = oper_kind == EcsOperAll; + if (match_all) { + const EcsType *type_ptr = ecs_get(world, elem->is.component, EcsType); + type = type_ptr->normalized; + } else { + type = elem->is.type; + } + + if (from_kind == EcsFromAny) { + if (!ecs_type_contains( + world, table_type, type, match_all, true)) + { + failure_info->reason = EcsMatchOrFromSelf; + return false; + } + } else if (from_kind == EcsFromOwned) { + if (!ecs_type_contains( + world, table_type, type, match_all, false)) + { + failure_info->reason = EcsMatchOrFromOwned; + return false; + } + } else if (from_kind == EcsFromShared) { + if (ecs_type_contains( + world, table_type, type, match_all, false) || + !ecs_type_contains( + world, table_type, type, match_all, true)) + { + failure_info->reason = EcsMatchOrFromShared; + return false; + } + } else if (from_kind == EcsFromParent) { + if (!(table->flags & EcsTableHasParent)) { + failure_info->reason = EcsMatchOrFromContainer; + return false; + } + + if (!components_contains( + world, table_type, type, NULL, match_all)) + { + failure_info->reason = EcsMatchOrFromContainer; + return false; + } + } + } + } + + return true; +} + +/** Match existing tables against system (table is created before system) */ +static +void match_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + int32_t i, count = ecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = ecs_sparse_get( + world->store.tables, ecs_table_t, i); + + if (ecs_query_match(world, table, query, NULL)) { + add_table(world, query, table); + } + } + + order_ranked_tables(world, query); +} + +#define ELEM(ptr, size, index) ECS_OFFSET(ptr, size * index) + +static +int32_t qsort_partition( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t elem_size, + int32_t lo, + int32_t hi, + ecs_compare_action_t compare) +{ + int32_t p = (hi + lo) / 2; + void *pivot = ELEM(ptr, elem_size, p); + ecs_entity_t pivot_e = entities[p]; + int32_t i = lo - 1, j = hi + 1; + void *el; + +repeat: + { + do { + i ++; + el = ELEM(ptr, elem_size, i); + } while ( compare(entities[i], el, pivot_e, pivot) < 0); + + do { + j --; + el = ELEM(ptr, elem_size, j); + } while ( compare(entities[j], el, pivot_e, pivot) > 0); + + if (i >= j) { + return j; + } + + ecs_table_swap(world, table, data, i, j); + + if (p == i) { + pivot = ELEM(ptr, elem_size, j); + pivot_e = entities[j]; + } else if (p == j) { + pivot = ELEM(ptr, elem_size, i); + pivot_e = entities[i]; + } + + goto repeat; + } +} + +static +void qsort_array( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t size, + int32_t lo, + int32_t hi, + ecs_compare_action_t compare) +{ + if ((hi - lo) < 1) { + return; + } + + int32_t p = qsort_partition( + world, table, data, entities, ptr, size, lo, hi, compare); + + qsort_array(world, table, data, entities, ptr, size, lo, p, compare); + + qsort_array(world, table, data, entities, ptr, size, p + 1, hi, compare); +} + +static +void sort_table( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_compare_action_t compare) +{ + ecs_data_t *data = ecs_table_get_data(table); + if (!data || !data->entities) { + /* Nothing to sort */ + return; + } + + int32_t count = ecs_table_data_count(data); + if (count < 2) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &data->columns[column_index]; + size = column->size; + ptr = ecs_vector_first_t(column->data, size, column->alignment); + } + + qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare); +} + +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_matched_table_t *table; + ecs_entity_t *entities; + const void *ptr; + int32_t row; + int32_t elem_size; + int32_t count; + bool shared; +} sort_helper_t; + +static +const void* ptr_from_helper( + sort_helper_t *helper) +{ + ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); + if (helper->shared) { + return helper->ptr; + } else { + return ELEM(helper->ptr, helper->elem_size, helper->row); + } +} + +static +ecs_entity_t e_from_helper( + sort_helper_t *helper) +{ + if (helper->row < helper->count) { + return helper->entities[helper->row]; + } else { + return 0; + } +} + +static +void build_sorted_table_range( + ecs_query_t *query, + int32_t start, + int32_t end) +{ + ecs_world_t *world = query->world; + ecs_entity_t component = query->sort_on_component; + ecs_compare_action_t compare = query->compare; + + /* Fetch data from all matched tables */ + ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t); + sort_helper_t *helper = ecs_os_malloc((end - start) * ECS_SIZEOF(sort_helper_t)); + + int i, to_sort = 0; + for (i = start; i < end; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->iter_data.table; + ecs_data_t *data = ecs_table_get_data(table); + ecs_vector_t *entities; + if (!data || !(entities = data->entities) || !ecs_table_count(table)) { + continue; + } + + int32_t index = ecs_type_index_of(table->type, component); + if (index != -1) { + ecs_column_t *column = &data->columns[index]; + int16_t size = column->size; + int16_t align = column->alignment; + helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align); + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; + } else if (component) { + /* Find component in prefab */ + ecs_entity_t base = ecs_find_entity_in_prefabs( + world, 0, table->type, component, 0); + + /* If a base was not found, the query should not have allowed using + * the component for sorting */ + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *cptr = ecs_get(world, component, EcsComponent); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + + helper[to_sort].ptr = ecs_get_w_entity(world, base, component); + helper[to_sort].elem_size = cptr->size; + helper[to_sort].shared = true; + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; + } + + helper[to_sort].table = table_data; + helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t); + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; + } + + ecs_table_slice_t *cur = NULL; + + bool proceed; + do { + int32_t j, min = 0; + proceed = true; + + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } + } + + if (!proceed) { + break; + } + + for (j = min + 1; j < to_sort; j++) { + ecs_entity_t e2 = e_from_helper(&helper[j]); + if (!e2) { + continue; + } + + const void *ptr1 = ptr_from_helper(&helper[min]); + const void *ptr2 = ptr_from_helper(&helper[j]); + + if (compare(e1, ptr1, e2, ptr2) > 0) { + min = j; + } + } + + sort_helper_t *cur_helper = &helper[min]; + + if (!cur || cur->table != cur_helper->table) { + cur = ecs_vector_add(&query->table_slices, ecs_table_slice_t); + ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); + cur->table = cur_helper->table; + cur->start_row = cur_helper->row; + cur->count = 1; + } else { + cur->count ++; + } + + cur_helper->row ++; + } while (proceed); + + ecs_os_free(helper); +} + +static +void build_sorted_tables( + ecs_query_t *query) +{ + /* Clean previous sorted tables */ + ecs_vector_free(query->table_slices); + query->table_slices = NULL; + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t); + ecs_matched_table_t *table = NULL; + + int32_t start = 0, rank = 0; + for (i = 0; i < count; i ++) { + table = &tables[i]; + if (rank != table->rank) { + if (start != i) { + build_sorted_table_range(query, start, i); + start = i; + } + rank = table->rank; + } + } + + if (start != i) { + build_sorted_table_range(query, start, i); + } +} + +static +bool tables_dirty( + ecs_query_t *query) +{ + if (query->needs_reorder) { + order_ranked_tables(query->world, query); + } + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first(query->tables, + ecs_matched_table_t); + bool is_dirty = false; + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->iter_data.table; + + if (!table_data->monitor) { + table_data->monitor = ecs_table_get_monitor(table); + is_dirty = true; + } + + int32_t *dirty_state = ecs_table_get_dirty_state(table); + int32_t t, type_count = table->column_count; + for (t = 0; t < type_count + 1; t ++) { + is_dirty = is_dirty || (dirty_state[t] != table_data->monitor[t]); + } + } + + is_dirty = is_dirty || (query->match_count != query->prev_match_count); + + return is_dirty; +} + +static +void tables_reset_dirty( + ecs_query_t *query) +{ + query->prev_match_count = query->match_count; + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->iter_data.table; + + if (!table_data->monitor) { + /* If one table doesn't have a monitor, none of the tables will have + * a monitor, so early out. */ + return; + } + + int32_t *dirty_state = ecs_table_get_dirty_state(table); + int32_t t, type_count = table->column_count; + for (t = 0; t < type_count + 1; t ++) { + table_data->monitor[t] = dirty_state[t]; + } + } +} + +static +void sort_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_compare_action_t compare = query->compare; + if (!compare) { + return; + } + + ecs_entity_t sort_on_component = query->sort_on_component; + + /* Iterate over active tables. Don't bother with inactive tables, since + * they're empty */ + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + bool tables_sorted = false; + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->iter_data.table; + + /* If no monitor had been created for the table yet, create it now */ + bool is_dirty = false; + if (!table_data->monitor) { + table_data->monitor = ecs_table_get_monitor(table); + + /* A new table is always dirty */ + is_dirty = true; + } + + int32_t *dirty_state = ecs_table_get_dirty_state(table); + + is_dirty = is_dirty || (dirty_state[0] != table_data->monitor[0]); + + int32_t index = -1; + if (sort_on_component) { + /* Get index of sorted component. We only care if the component we're + * sorting on has changed or if entities have been added / re(moved) */ + index = ecs_type_index_of(table->type, sort_on_component); + if (index != -1) { + ecs_assert(index < ecs_vector_count(table->type), ECS_INTERNAL_ERROR, NULL); + is_dirty = is_dirty || (dirty_state[index + 1] != table_data->monitor[index + 1]); + } else { + /* Table does not contain component which means the sorted + * component is shared. Table does not need to be sorted */ + continue; + } + } + + /* Check both if entities have moved (element 0) or if the component + * we're sorting on has changed (index + 1) */ + if (is_dirty) { + /* Sort the table */ + sort_table(world, table, index, compare); + tables_sorted = true; + } + } + + if (tables_sorted || query->match_count != query->prev_match_count) { + build_sorted_tables(query); + query->match_count ++; /* Increase version if tables changed */ + } +} + +static +bool has_refs( + ecs_sig_t *sig) +{ + int32_t i, count = ecs_vector_count(sig->columns); + ecs_sig_column_t *columns = ecs_vector_first(sig->columns, ecs_sig_column_t); + + for (i = 0; i < count; i ++) { + ecs_sig_from_kind_t from_kind = columns[i].from_kind; + + if (columns[i].oper_kind == EcsOperNot && from_kind == EcsFromEmpty) { + /* Special case: if oper kind is Not and the query contained a + * shared expression, the expression is translated to FromId to + * prevent resolving the ref */ + return true; + } else if (from_kind != EcsFromAny && from_kind != EcsFromEmpty) { + /* If the component is not from the entity being iterated over, and + * the column is not just passing an id, it must be a reference to + * another entity. */ + return true; + } + } + + return false; +} + +static +bool has_traits( + ecs_sig_t *sig) +{ + int32_t i, count = ecs_vector_count(sig->columns); + ecs_sig_column_t *columns = ecs_vector_first(sig->columns, ecs_sig_column_t); + + for (i = 0; i < count; i ++) { + if (is_column_trait(&columns[i])) { + return true; + } + } + + return false; +} + +static +void register_monitors( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_vector_each(query->sig.columns, ecs_sig_column_t, column, { + /* If component is requested with CASCADE source register component as a + * parent monitor. Parent monitors keep track of whether an entity moved + * in the hierarchy, which potentially requires the query to reorder its + * tables. + * Also register a regular component monitor for EcsCascade columns. + * This ensures that when the component used in the CASCADE column + * is added or removed tables are updated accordingly*/ + if (column->from_kind == EcsCascade) { + if (column->oper_kind != EcsOperOr) { + ecs_component_monitor_register( + &world->parent_monitors, column->is.component, query); + + ecs_component_monitor_register( + &world->component_monitors, column->is.component, query); + } else { + ecs_vector_each(column->is.type, ecs_entity_t, e_ptr, { + ecs_component_monitor_register( + &world->parent_monitors, *e_ptr, query); + + ecs_component_monitor_register( + &world->component_monitors, *e_ptr, query); + }); + } + + /* FromSelf also requires registering a monitor, as FromSelf columns can + * be matched with prefabs. The only column kinds that do not require + * registering a monitor are FromOwned and FromNothing. */ + } else if (column->from_kind == EcsFromAny || + column->from_kind == EcsFromShared || + column->from_kind == EcsFromEntity || + column->from_kind == EcsFromParent) + { + if (column->oper_kind != EcsOperOr) { + ecs_component_monitor_register( + &world->component_monitors, column->is.component, query); + } else { + ecs_vector_each(column->is.type, ecs_entity_t, e_ptr, { + ecs_component_monitor_register( + &world->component_monitors, *e_ptr, query); + }); + } + } + }); +} + +static +void process_signature( + ecs_world_t *world, + ecs_query_t *query) +{ + int i, count = ecs_vector_count(query->sig.columns); + ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t); + + for (i = 0; i < count; i ++) { + ecs_sig_column_t *column = &columns[i]; + ecs_sig_oper_kind_t op = column->oper_kind; + ecs_sig_from_kind_t from = column->from_kind; + ecs_sig_inout_kind_t inout = column->inout_kind; + + if (inout != EcsIn) { + query->flags |= EcsQueryHasOutColumns; + } + + if (op == EcsOperOptional) { + query->flags |= EcsQueryHasOptional; + } + + if (!(query->flags & EcsQueryMatchDisabled)) { + if (op == EcsOperOr) { + /* If the signature explicitly indicates interest in EcsDisabled, + * signal that disabled entities should be matched. By default, + * disabled entities are not matched. */ + if (ecs_type_owns_entity( + world, column->is.type, EcsDisabled, true)) + { + query->flags |= EcsQueryMatchDisabled; + } + } else if (op == EcsOperAnd || op == EcsOperOptional) { + if (column->is.component == EcsDisabled) { + query->flags |= EcsQueryMatchDisabled; + } + } + } + + if (!(query->flags & EcsQueryMatchPrefab)) { + if (op == EcsOperOr) { + /* If the signature explicitly indicates interest in EcsPrefab, + * signal that disabled entities should be matched. By default, + * prefab entities are not matched. */ + if (ecs_type_owns_entity( + world, column->is.type, EcsPrefab, true)) + { + query->flags |= EcsQueryMatchPrefab; + } + } else if (op == EcsOperAnd || op == EcsOperOptional) { + if (column->is.component == EcsPrefab) { + query->flags |= EcsQueryMatchPrefab; + } + } + } + + if (from == EcsFromAny || + from == EcsFromOwned || + from == EcsFromShared || + from == EcsFromParent) + { + query->flags |= EcsQueryNeedsTables; + } + + if (from == EcsCascade) { + query->cascade_by = i + 1; + query->rank_on_component = column->is.component; + } + + if (from == EcsFromEntity) { + ecs_assert(column->source != 0, ECS_INTERNAL_ERROR, NULL); + ecs_set_watch(world, column->source); + } + } + + query->flags |= (ecs_flags32_t)(has_refs(&query->sig) * EcsQueryHasRefs); + query->flags |= (ecs_flags32_t)(has_traits(&query->sig) * EcsQueryHasTraits); + + if (!(query->flags & EcsQueryIsSubquery)) { + register_monitors(world, query); + } +} + +static +bool match_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + if (ecs_query_match(world, table, query, NULL)) { + add_table(world, query, table); + return true; + } + return false; +} + +/* Move table from empty to non-empty list, or vice versa */ +static +int32_t move_table( + ecs_query_t *query, + ecs_table_t *table, + int32_t index, + ecs_vector_t **dst_array, + ecs_vector_t *src_array, + bool activate) +{ + (void)table; + + int32_t new_index = 0; + int32_t last_src_index = ecs_vector_count(src_array) - 1; + ecs_assert(last_src_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_matched_table_t *mt = ecs_vector_last(src_array, ecs_matched_table_t); + + /* The last table of the source array will be moved to the location of the + * table to move, do some bookkeeping to keep things consistent. */ + if (last_src_index) { + ecs_table_indices_t *ti = ecs_map_get(query->table_indices, + ecs_table_indices_t, mt->iter_data.table->id); + + int i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t old_index = ti->indices[i]; + if (activate) { + if (old_index >= 0) { + /* old_index should be negative if activate is true, since + * we're moving from the empty list to the non-empty list. + * However, if the last table in the source array is also + * the table being moved, this can happen. */ + ecs_assert(table == mt->iter_data.table, + ECS_INTERNAL_ERROR, NULL); + continue; + } + /* If activate is true, src = the empty list, and index should + * be negative. */ + old_index = old_index * -1 - 1; /* Normalize */ + } + + /* Ensure to update correct index, as there can be more than one */ + if (old_index == last_src_index) { + if (activate) { + ti->indices[i] = index * -1 - 1; + } else { + ti->indices[i] = index; + } + break; + } + } + + /* If the src array contains tables, there must be a table that will get + * moved. */ + ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); + } else { + /* If last_src_index is 0, the table to move was the only table in the + * src array, so no other administration needs to be updated. */ + } + + /* Actually move the table. Only move from src to dst if we have a + * dst_array, otherwise just remove it from src. */ + if (dst_array) { + new_index = ecs_vector_count(*dst_array); + ecs_vector_move_index(dst_array, src_array, ecs_matched_table_t, index); + + /* Make sure table is where we expect it */ + mt = ecs_vector_last(*dst_array, ecs_matched_table_t); + ecs_assert(mt->iter_data.table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(*dst_array) == (new_index + 1), + ECS_INTERNAL_ERROR, NULL); + } else { + ecs_vector_remove_index(src_array, ecs_matched_table_t, index); + } + + /* Ensure that src array has now one element less */ + ecs_assert(ecs_vector_count(src_array) == last_src_index, + ECS_INTERNAL_ERROR, NULL); + + /* Return new index for table */ + if (activate) { + /* Table is now active, index is positive */ + return new_index; + } else { + /* Table is now inactive, index is negative */ + return new_index * -1 - 1; + } +} + +/** Table activation happens when a table was or becomes empty. Deactivated + * tables are not considered by the system in the main loop. */ +static +void activate_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table, + bool active) +{ + ecs_vector_t *src_array, *dst_array; + int32_t activated = 0; + + if (active) { + src_array = query->empty_tables; + dst_array = query->tables; + } else { + src_array = query->tables; + dst_array = query->empty_tables; + } + + ecs_table_indices_t *ti = ecs_map_get( + query->table_indices, ecs_table_indices_t, table->id); + + if (ti) { + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t index = ti->indices[i]; + + if (index < 0) { + if (!active) { + /* If table is already inactive, no need to move */ + continue; + } + index = index * -1 - 1; + } else { + if (active) { + /* If table is already active, no need to move */ + continue; + } + } + + ecs_matched_table_t *mt = ecs_vector_get( + src_array, ecs_matched_table_t, index); + ecs_assert(mt->iter_data.table == table, ECS_INTERNAL_ERROR, NULL); + (void)mt; + + activated ++; + + ti->indices[i] = move_table( + query, table, index, &dst_array, src_array, active); + } + + if (activated) { + /* Activate system if registered with query */ +#ifdef FLECS_SYSTEMS_H + if (query->system) { + int32_t dst_count = ecs_vector_count(dst_array); + if (active) { + if (dst_count == 1) { + ecs_system_activate(world, query->system, true, NULL); + } + } else if (ecs_vector_count(src_array) == 0) { + ecs_system_activate(world, query->system, false, NULL); + } + } +#endif + } + + if (active) { + query->tables = dst_array; + } else { + query->empty_tables = dst_array; + } + } + + if (!activated) { + /* Received an activate event for a table we're not matched with. This + * can only happen if this is a subquery */ + ecs_assert((query->flags & EcsQueryIsSubquery) != 0, + ECS_INTERNAL_ERROR, NULL); + return; + } + + /* Signal query it needs to reorder tables. Doing this in place could slow + * down scenario's where a large number of tables is matched with an ordered + * query. Since each table would trigger the activate signal, there would be + * as many sorts as added tables, vs. only one when ordering happens when an + * iterator is obtained. */ + query->needs_reorder = true; +} + +static +void add_subquery( + ecs_world_t *world, + ecs_query_t *parent, + ecs_query_t *subquery) +{ + ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); + *elem = subquery; + + /* Iterate matched tables, match them with subquery */ + ecs_matched_table_t *tables = ecs_vector_first(parent->tables, ecs_matched_table_t); + int32_t i, count = ecs_vector_count(parent->tables); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table = &tables[i]; + match_table(world, subquery, table->iter_data.table); + activate_table(world, subquery, table->iter_data.table, true); + } + + /* Do the same for inactive tables */ + tables = ecs_vector_first(parent->empty_tables, ecs_matched_table_t); + count = ecs_vector_count(parent->empty_tables); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table = &tables[i]; + match_table(world, subquery, table->iter_data.table); + } +} + +static +void notify_subqueries( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + if (query->subqueries) { + ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); + int32_t i, count = ecs_vector_count(query->subqueries); + + ecs_query_event_t sub_event = *event; + sub_event.parent_query = query; + + for (i = 0; i < count; i ++) { + ecs_query_t *sub = queries[i]; + ecs_query_notify(world, sub, &sub_event); + } + } +} + +static +void free_matched_table( + ecs_matched_table_t *table) +{ + ecs_os_free(table->iter_data.columns); + ecs_os_free(table->iter_data.components); + ecs_os_free((ecs_vector_t**)table->iter_data.types); + ecs_os_free(table->iter_data.references); + ecs_os_free(table->sparse_columns); + ecs_os_free(table->bitset_columns); + ecs_os_free(table->monitor); +} + +/** Check if a table was matched with the system */ +static +ecs_table_indices_t* get_table_indices( + ecs_query_t *query, + ecs_table_t *table) +{ + return ecs_map_get(query->table_indices, ecs_table_indices_t, table->id); +} + +static +void resolve_cascade_container( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_indices_t *ti, + ecs_type_t table_type) +{ + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t table_data_index = ti->indices[i]; + ecs_matched_table_t *table_data; + + if (table_data_index >= 0) { + table_data = ecs_vector_get( + query->tables, ecs_matched_table_t, table_data_index); + } else { + table_data = ecs_vector_get( + query->empty_tables, ecs_matched_table_t, table_data_index); + } + + ecs_assert(table_data->iter_data.references != 0, ECS_INTERNAL_ERROR, NULL); + + /* Obtain reference index */ + int32_t *column_indices = table_data->iter_data.columns; + int32_t column = query->cascade_by - 1; + int32_t ref_index = -column_indices[column] - 1; + + /* Obtain pointer to the reference data */ + ecs_ref_t *references = table_data->iter_data.references; + ecs_ref_t *ref = &references[ref_index]; + ecs_assert(ref->component == get_cascade_component(query), + ECS_INTERNAL_ERROR, NULL); + + /* Resolve container entity */ + ecs_entity_t container = ecs_find_in_type( + world, table_type, ref->component, ECS_CHILDOF); + + /* If container was found, update the reference */ + if (container) { + references[ref_index].entity = container; + ecs_get_ref_w_entity( + world, &references[ref_index], container, + ref->component); + } else { + references[ref_index].entity = 0; + } + } +} + +/* Remove table */ +static +void remove_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_vector_t *tables, + int32_t index, + bool empty) +{ + ecs_matched_table_t *mt = ecs_vector_get( + tables, ecs_matched_table_t, index); + if (!mt) { + /* Query was notified of a table it doesn't match with, this can only + * happen if query is a subquery. */ + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + return; + } + + ecs_assert(mt->iter_data.table == table, ECS_INTERNAL_ERROR, NULL); + (void)table; + + /* Free table before moving, as the move will cause another table to occupy + * the memory of mt */ + free_matched_table(mt); + move_table(query, mt->iter_data.table, index, NULL, tables, empty); +} + +static +void unmatch_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_table_indices_t *ti) +{ + if (!ti) { + ti = get_table_indices(query, table); + if (!ti) { + return; + } + } + + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t index = ti->indices[i]; + if (index < 0) { + index = index * -1 - 1; + remove_table(query, table, query->empty_tables, index, true); + } else { + remove_table(query, table, query->tables, index, false); + } + } + + ecs_os_free(ti->indices); + ecs_map_remove(query->table_indices, table->id); +} + +static +void rematch_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + ecs_table_indices_t *match = get_table_indices(query, table); + + if (ecs_query_match(world, table, query, NULL)) { + /* If the table matches, and it is not currently matched, add */ + if (match == NULL) { + add_table(world, query, table); + + /* If table still matches and has cascade column, reevaluate the + * sources of references. This may have changed in case + * components were added/removed to container entities */ + } else if (query->cascade_by) { + resolve_cascade_container( + world, query, match, table->type); + + /* If query has optional columns, it is possible that a column that + * previously had data no longer has data, or vice versa. Do a full + * rematch to make sure data is consistent. */ + } else if (query->flags & EcsQueryHasOptional) { + unmatch_table(query, table, match); + if (!(query->flags & EcsQueryIsSubquery)) { + ecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + add_table(world, query, table); + } + } else { + /* Table no longer matches, remove */ + if (match != NULL) { + unmatch_table(query, table, match); + if (!(query->flags & EcsQueryIsSubquery)) { + ecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + } + } +} + +/* Rematch system with tables after a change happened to a watched entity */ +static +void rematch_tables( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_t *parent_query) +{ + if (parent_query) { + ecs_matched_table_t *tables = ecs_vector_first(parent_query->tables, ecs_matched_table_t); + int32_t i, count = ecs_vector_count(parent_query->tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = tables[i].iter_data.table; + rematch_table(world, query, table); + } + + tables = ecs_vector_first(parent_query->empty_tables, ecs_matched_table_t); + count = ecs_vector_count(parent_query->empty_tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = tables[i].iter_data.table; + rematch_table(world, query, table); + } + } else { + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = ecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + /* Is the system currently matched with the table? */ + ecs_table_t *table = ecs_sparse_get(tables, ecs_table_t, i); + rematch_table(world, query, table); + } + } + + group_tables(world, query); + order_ranked_tables(world, query); + + /* Enable/disable system if constraints are (not) met. If the system is + * already dis/enabled this operation has no side effects. */ + if (query->system) { + if (ecs_sig_check_constraints(world, &query->sig)) { + ecs_remove_entity(world, query->system, EcsDisabledIntern); + } else { + ecs_add_entity(world, query->system, EcsDisabledIntern); + } + } +} + +static +void remove_subquery( + ecs_query_t *parent, + ecs_query_t *sub) +{ + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->subqueries != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vector_count(parent->subqueries); + ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); + + for (i = 0; i < count; i ++) { + if (sq[i] == sub) { + break; + } + } + + ecs_vector_remove_index(parent->subqueries, ecs_query_t*, i); +} + +/* -- Private API -- */ + +void ecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + bool notify = true; + + switch(event->kind) { + case EcsQueryTableMatch: + /* Creation of new table */ + if (match_table(world, query, event->table)) { + if (query->subqueries) { + notify_subqueries(world, query, event); + } + } + notify = false; + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + unmatch_table(query, event->table, NULL); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + rematch_tables(world, query, event->parent_query); + break; + case EcsQueryTableEmpty: + /* Table is empty, deactivate */ + activate_table(world, query, event->table, false); + break; + case EcsQueryTableNonEmpty: + /* Table is non-empty, activate */ + activate_table(world, query, event->table, true); + break; + case EcsQueryOrphan: + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + query->flags |= EcsQueryIsOrphaned; + query->parent = NULL; + break; + } + + if (notify) { + notify_subqueries(world, query, event); + } +} + +/* -- Public API -- */ + +static +ecs_query_t* ecs_query_new_w_sig_intern( + ecs_world_t *world, + ecs_entity_t system, + ecs_sig_t *sig, + bool is_subquery) +{ + ecs_query_t *result = ecs_os_calloc(sizeof(ecs_query_t)); + result->world = world; + result->sig = *sig; + result->table_indices = ecs_map_new(ecs_table_indices_t, 0); + result->tables = ecs_vector_new(ecs_matched_table_t, 0); + result->empty_tables = ecs_vector_new(ecs_matched_table_t, 0); + result->system = system; + result->prev_match_count = -1; + + if (is_subquery) { + result->flags |= EcsQueryIsSubquery; + } + + process_signature(world, result); + + ecs_trace_2("query #[green]%s#[reset] created with expression #[red]%s", + query_name(world, result), result->sig.expr); + + ecs_log_push(); + + if (!is_subquery) { + /* Register query with world */ + ecs_query_t **elem = ecs_vector_add(&world->queries, ecs_query_t*); + *elem = result; + + if (result->flags & EcsQueryNeedsTables) { + if (ecs_has_entity(world, system, EcsMonitor)) { + result->flags |= EcsQueryMonitor; + } + + if (ecs_has_entity(world, system, EcsOnSet)) { + result->flags |= EcsQueryOnSet; + } + + if (ecs_has_entity(world, system, EcsUnSet)) { + result->flags |= EcsQueryUnSet; + } + + match_tables(world, result); + } else { + /* Add stub table that resolves references (if any) so everything is + * preprocessed when the query is evaluated. */ + add_table(world, result, NULL); + } + } + + if (result->cascade_by) { + result->group_table = rank_by_depth; + } + + ecs_log_pop(); + + /* Make sure application can't try to free sig resources */ + *sig = (ecs_sig_t){ 0 }; + + return result; +} + +ecs_query_t* ecs_query_new_w_sig( + ecs_world_t *world, + ecs_entity_t system, + ecs_sig_t *sig) +{ + return ecs_query_new_w_sig_intern(world, system, sig, false); +} + +ecs_query_t* ecs_query_new( + ecs_world_t *world, + const char *expr) +{ + ecs_sig_t sig = { 0 }; + ecs_sig_init(world, NULL, expr, &sig); + return ecs_query_new_w_sig(world, 0, &sig); +} + +ecs_query_t* ecs_subquery_new( + ecs_world_t *world, + ecs_query_t *parent, + const char *expr) +{ + ecs_sig_t sig = { 0 }; + ecs_sig_init(world, NULL, expr, &sig); + ecs_query_t *result = ecs_query_new_w_sig_intern(world, 0, &sig, true); + result->parent = parent; + add_subquery(world, parent, result); + return result; +} + +ecs_sig_t* ecs_query_get_sig( + ecs_query_t *query) +{ + return &query->sig; +} + +void ecs_query_free( + ecs_query_t *query) +{ + ecs_world_t *world = query->world; + + if ((query->flags & EcsQueryIsSubquery) && + !(query->flags & EcsQueryIsOrphaned)) + { + remove_subquery(query->parent, query); + } + + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryOrphan + }); + + ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, { + free_matched_table(table); + }); + + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + free_matched_table(table); + }); + + ecs_map_iter_t it = ecs_map_iter(query->table_indices); + ecs_table_indices_t *ti; + while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) { + ecs_os_free(ti->indices); + } + ecs_map_free(query->table_indices); + + ecs_vector_free(query->subqueries); + ecs_vector_free(query->tables); + ecs_vector_free(query->empty_tables); + ecs_vector_free(query->table_slices); + ecs_sig_deinit(&query->sig); + + /* Find query in vector */ + if (!(query->flags & EcsQueryIsSubquery) && world->queries) { + int32_t index = -1; + ecs_vector_each(world->queries, ecs_query_t*, q_ptr, { + if (*q_ptr == query) { + index = q_ptr_i; + } + }); + + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + ecs_vector_remove_index(world->queries, ecs_query_t*, index); + } + + ecs_os_free(query); +} + +/* Create query iterator */ +ecs_iter_t ecs_query_iter_page( + ecs_query_t *query, + int32_t offset, + int32_t limit) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + + ecs_world_t *world = query->world; + + if (query->needs_reorder) { + order_ranked_tables(world, query); + } + + sort_tables(world, query); + + if (!world->in_progress && query->flags & EcsQueryHasRefs) { + ecs_eval_component_monitors(world); + } + + tables_reset_dirty(query); + + int32_t table_count; + if (query->table_slices) { + table_count = ecs_vector_count(query->table_slices); + } else { + table_count = ecs_vector_count(query->tables); + } + + ecs_query_iter_t it = { + .page_iter = { + .offset = offset, + .limit = limit, + .remaining = limit + }, + .index = 0, + }; + + return (ecs_iter_t){ + .world = world, + .query = query, + .column_count = ecs_vector_count(query->sig.columns), + .table_count = table_count, + .inactive_table_count = ecs_vector_count(query->empty_tables), + .iter.query = it + }; +} + +ecs_iter_t ecs_query_iter( + ecs_query_t *query) +{ + return ecs_query_iter_page(query, 0, 0); +} + +void ecs_query_set_iter( + ecs_world_t *world, + ecs_query_t *query, + ecs_iter_t *it, + int32_t table_index, + int32_t row, + int32_t count) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + + ecs_matched_table_t *table_data = ecs_vector_get( + query->tables, ecs_matched_table_t, table_index); + ecs_assert(table_data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = table_data->iter_data.table; + ecs_data_t *data = ecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entity_buffer = ecs_vector_first(data->entities, ecs_entity_t); + it->entities = &entity_buffer[row]; + + it->world = world; + it->query = query; + it->column_count = ecs_vector_count(query->sig.columns); + it->table_count = 1; + it->inactive_table_count = 0; + it->table_columns = data->columns; + it->table = &table_data->iter_data; + it->offset = row; + it->count = count; + it->total_count = count; +} + +static +int ecs_page_iter_next( + ecs_page_iter_t *it, + ecs_page_cursor_t *cur) +{ + int32_t offset = it->offset; + int32_t limit = it->limit; + if (!(offset || limit)) { + return cur->count == 0; + } + + int32_t count = cur->count; + int32_t remaining = it->remaining; + + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + it->offset -= count; + return 1; + } else { + cur->first += offset; + count = cur->count -= offset; + it->offset = 0; + } + } + + if (remaining) { + if (remaining > count) { + it->remaining -= count; + } else { + count = cur->count = remaining; + it->remaining = 0; + } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + return -1; + } + + return count == 0; +} + +static +int find_smallest_column( + ecs_table_t *table, + ecs_matched_table_t *table_data, + ecs_vector_t *sparse_columns) +{ + ecs_sparse_column_t *sparse_column_array = + ecs_vector_first(sparse_columns, ecs_sparse_column_t); + int32_t i, count = ecs_vector_count(sparse_columns); + int32_t min = INT_MAX, index = 0; + + for (i = 0; i < count; i ++) { + /* The array with sparse queries for the matched table */ + ecs_sparse_column_t *sparse_column = &sparse_column_array[i]; + + /* Pointer to the switch column struct of the table */ + ecs_sw_column_t *sc = sparse_column->sw_column; + + /* If the sparse column pointer hadn't been retrieved yet, do it now */ + if (!sc) { + /* Get the table column index from the signature column index */ + int32_t table_column_index = table_data->iter_data.columns[ + sparse_column->signature_column_index]; + + /* Translate the table column index to switch column index */ + table_column_index -= table->sw_column_offset; + ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + + /* Get the sparse column */ + ecs_data_t *data = ecs_table_get_data(table); + sc = sparse_column->sw_column = + &data->sw_columns[table_column_index - 1]; + } + + /* Find the smallest column */ + ecs_switch_t *sw = sc->data; + int32_t case_count = ecs_switch_case_count(sw, sparse_column->sw_case); + if (case_count < min) { + min = case_count; + index = i + 1; + } + } + + return index; +} + +static +int sparse_column_next( + ecs_table_t *table, + ecs_matched_table_t *matched_table, + ecs_vector_t *sparse_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur) +{ + bool first_iteration = false; + int32_t sparse_smallest; + + if (!(sparse_smallest = iter->sparse_smallest)) { + sparse_smallest = iter->sparse_smallest = find_smallest_column( + table, matched_table, sparse_columns); + first_iteration = true; + } + + sparse_smallest -= 1; + + ecs_sparse_column_t *columns = ecs_vector_first( + sparse_columns, ecs_sparse_column_t); + ecs_sparse_column_t *column = &columns[sparse_smallest]; + ecs_switch_t *sw, *sw_smallest = column->sw_column->data; + ecs_entity_t case_smallest = column->sw_case; + + /* Find next entity to iterate in sparse column */ + int32_t first; + if (first_iteration) { + first = ecs_switch_first(sw_smallest, case_smallest); + } else { + first = ecs_switch_next(sw_smallest, iter->sparse_first); + } + + if (first == -1) { + goto done; + } + + /* Check if entity matches with other sparse columns, if any */ + int32_t i, count = ecs_vector_count(sparse_columns); + do { + for (i = 0; i < count; i ++) { + if (i == sparse_smallest) { + /* Already validated this one */ + continue; + } + + column = &columns[i]; + sw = column->sw_column->data; + + if (ecs_switch_get(sw, first) != column->sw_case) { + first = ecs_switch_next(sw_smallest, first); + if (first == -1) { + goto done; + } + } + } + } while (i != count); + + cur->first = iter->sparse_first = first; + cur->count = 1; + + return 0; +done: + /* Iterated all elements in the sparse list, we should move to the + * next matched table. */ + iter->sparse_smallest = 0; + iter->sparse_first = 0; + + return -1; +} + +#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) + +static +int bitset_column_next( + ecs_table_t *table, + ecs_vector_t *bitset_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur) +{ + /* Precomputed single-bit test */ + static const uint64_t bitmask[64] = { + (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, + (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, + (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, + (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, + (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, + (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, + (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, + (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, + (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, + (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, + (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, + (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, + (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, + (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, + (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, + (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 + }; + + /* Precomputed test to verify if remainder of block is set (or not) */ + static const uint64_t bitmask_remain[64] = { + BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), + BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), + BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), + BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), + BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), + BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), + BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), + BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), + BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), + BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), + BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), + BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), + BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), + BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), + BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), + BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), + BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), + BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), + BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), + BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), + BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), + BS_MAX - (BS_MAX >> 1) + }; + + int32_t i, count = ecs_vector_count(bitset_columns); + ecs_bitset_column_t *columns = ecs_vector_first( + bitset_columns, ecs_bitset_column_t); + int32_t bs_offset = table->bs_column_offset; + + int32_t first = iter->bitset_first; + int32_t last = 0; + + for (i = 0; i < count; i ++) { + ecs_bitset_column_t *column = &columns[i]; + ecs_bs_column_t *bs_column = columns[i].bs_column; + + if (!bs_column) { + ecs_data_t *data = table->data; + int32_t index = column->column_index; + ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); + bs_column = &data->bs_columns[index - bs_offset]; + columns[i].bs_column = bs_column; + } + + ecs_bitset_t *bs = &bs_column->data; + int32_t bs_elem_count = bs->count; + int32_t bs_block = first >> 6; + int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; + + if (bs_block >= bs_block_count) { + goto done; + } + + uint64_t *data = bs->data; + int32_t bs_start = first & 0x3F; + + /* Step 1: find the first non-empty block */ + uint64_t v = data[bs_block]; + uint64_t remain = bitmask_remain[bs_start]; + while (!(v & remain)) { + /* If no elements are remaining, move to next block */ + if ((++bs_block) >= bs_block_count) { + /* No non-empty blocks left */ + goto done; + } + + bs_start = 0; + remain = BS_MAX; /* Test the full block */ + v = data[bs_block]; + } + + /* Step 2: find the first non-empty element in the block */ + while (!(v & bitmask[bs_start])) { + bs_start ++; + + /* Block was not empty, so bs_start must be smaller than 64 */ + ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); + } + + /* Step 3: Find number of contiguous enabled elements after start */ + int32_t bs_end = bs_start, bs_block_end = bs_block; + + remain = bitmask_remain[bs_end]; + while ((v & remain) == remain) { + bs_end = 0; + bs_block_end ++; + + if (bs_block_end == bs_block_count) { + break; + } + + v = data[bs_block_end]; + remain = BS_MAX; /* Test the full block */ + } + + /* Step 4: find remainder of enabled elements in current block */ + if (bs_block_end != bs_block_count) { + while ((v & bitmask[bs_end])) { + bs_end ++; + } + } + + /* Block was not 100% occupied, so bs_start must be smaller than 64 */ + ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); + + /* Step 5: translate to element start/end and make sure that each column + * range is a subset of the previous one. */ + first = bs_block * 64 + bs_start; + int32_t cur_last = bs_block_end * 64 + bs_end; + + /* No enabled elements found in table */ + if (first == cur_last) { + goto done; + } + + /* If multiple bitsets are evaluated, make sure each subsequent range + * is equal or a subset of the previous range */ + if (i) { + /* If the first element of a subsequent bitset is larger than the + * previous last value, start over. */ + if (first >= last) { + i = -1; + continue; + } + + /* Make sure the last element of the range doesn't exceed the last + * element of the previous range. */ + if (cur_last > last) { + cur_last = last; + } + } + + last = cur_last; + int32_t elem_count = last - first; + + /* Make sure last element doesn't exceed total number of elements in + * the table */ + if (elem_count > bs_elem_count) { + elem_count = bs_elem_count; + } + + cur->first = first; + cur->count = elem_count; + iter->bitset_first = first; + } + + /* Keep track of last processed element for iteration */ + iter->bitset_first = last; + + return 0; +done: + return -1; +} + +static +void mark_columns_dirty( + ecs_query_t *query, + ecs_matched_table_t *table_data) +{ + ecs_table_t *table = table_data->iter_data.table; + + if (table && table->dirty_state) { + int32_t i, count = ecs_vector_count(query->sig.columns); + ecs_sig_column_t *columns = ecs_vector_first( + query->sig.columns, ecs_sig_column_t); + + for (i = 0; i < count; i ++) { + if (columns[i].inout_kind != EcsIn) { + int32_t table_column = table_data->iter_data.columns[i]; + if (table_column > 0) { + table->dirty_state[table_column] ++; + } + } + } + } +} + +/* Return next table */ +bool ecs_query_next( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_iter_t *iter = &it->iter.query; + ecs_page_iter_t *piter = &iter->page_iter; + ecs_world_t *world = it->world; + ecs_query_t *query = it->query; + + ecs_get_stage(&world); + ecs_table_slice_t *slice = ecs_vector_first( + query->table_slices, ecs_table_slice_t); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + + ecs_assert(!slice || query->compare, ECS_INTERNAL_ERROR, NULL); + + ecs_page_cursor_t cur; + int32_t table_count = it->table_count; + int32_t prev_count = it->total_count; + + int i; + for (i = iter->index; i < table_count; i ++) { + ecs_matched_table_t *table_data = slice ? slice[i].table : &tables[i]; + ecs_table_t *table = table_data->iter_data.table; + ecs_data_t *data = NULL; + + iter->index = i + 1; + + if (table) { + ecs_vector_t *bitset_columns = table_data->bitset_columns; + ecs_vector_t *sparse_columns = table_data->sparse_columns; + data = ecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + it->table_columns = data->columns; + + if (slice) { + cur.first = slice[i].start_row; + cur.count = slice[i].count; + } else { + cur.first = 0; + cur.count = ecs_table_count(table); + } + + if (cur.count) { + if (bitset_columns) { + + if (bitset_column_next(table, bitset_columns, iter, + &cur) == -1) + { + /* No more enabled components for table */ + continue; + } else { + iter->index = i; + } + } + + if (sparse_columns) { + if (sparse_column_next(table, table_data, + sparse_columns, iter, &cur) == -1) + { + /* No more elements in sparse column */ + continue; + } else { + iter->index = i; + } + } + + int ret = ecs_page_iter_next(piter, &cur); + if (ret < 0) { + return false; + } else if (ret > 0) { + continue; + } + } else { + continue; + } + + ecs_entity_t *entity_buffer = ecs_vector_first( + data->entities, ecs_entity_t); + it->entities = &entity_buffer[cur.first]; + it->offset = cur.first; + it->count = cur.count; + it->total_count = cur.count; + } + + it->table = &table_data->iter_data; + it->frame_offset += prev_count; + + if (query->flags & EcsQueryHasOutColumns) { + if (table) { + mark_columns_dirty(query, table_data); + } + } + + return true; + } + + return false; +} + +bool ecs_query_next_w_filter( + ecs_iter_t *iter, + const ecs_filter_t *filter) +{ + ecs_table_t *table; + + do { + if (!ecs_query_next(iter)) { + return false; + } + table = iter->table->table; + } while (filter && !ecs_table_match_filter(iter->world, table, filter)); + + return true; +} + +bool ecs_query_next_worker( + ecs_iter_t *it, + int32_t current, + int32_t total) +{ + int32_t per_worker, first, prev_offset = it->offset; + + do { + if (!ecs_query_next(it)) { + return false; + } + + int32_t count = it->count; + per_worker = count / total; + first = per_worker * current; + + count -= per_worker * total; + + if (count) { + if (current < count) { + per_worker ++; + first += current; + } else { + first += count; + } + } + + if (!per_worker && !(it->query->flags & EcsQueryNeedsTables)) { + if (current == 0) { + return true; + } else { + return false; + } + } + } while (!per_worker); + + it->frame_offset -= prev_offset; + it->count = per_worker; + it->offset += first; + it->entities = &it->entities[first]; + it->frame_offset += first; + + return true; +} + +void ecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t sort_component, + ecs_compare_action_t compare) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); + + query->sort_on_component = sort_component; + query->compare = compare; + + ecs_vector_free(query->table_slices); + query->table_slices = NULL; + + sort_tables(world, query); + + if (!query->table_slices) { + build_sorted_tables(query); + } +} + +void ecs_query_group_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t sort_component, + ecs_rank_type_action_t group_table_action) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); + + query->rank_on_component = sort_component; + query->group_table = group_table_action; + + group_tables(world, query); + + order_ranked_tables(world, query); + + build_sorted_tables(query); +} + +bool ecs_query_changed( + ecs_query_t *query) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + return tables_dirty(query); +} + +bool ecs_query_orphaned( + ecs_query_t *query) +{ + return query->flags & EcsQueryIsOrphaned; +} + +const EcsComponent* ecs_component_from_id( + ecs_world_t *world, + ecs_entity_t e) +{ + ecs_entity_t trait = 0; + + /* If this is a trait, get the trait component from the identifier */ + if (ECS_HAS_ROLE(e, TRAIT)) { + trait = e; + e = e & ECS_COMPONENT_MASK; + e = ecs_entity_t_hi(e); + } + + const EcsComponent *component = ecs_get(world, e, EcsComponent); + if (!component && trait) { + /* If this is a trait column and the trait is not a component, use + * the component type of the component the trait is applied to. */ + e = ecs_entity_t_lo(trait); + component = ecs_get(world, e, EcsComponent); + } + + ecs_assert(!component || !ECS_HAS_ROLE(e, CHILDOF), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!component || !ECS_HAS_ROLE(e, INSTANCEOF), ECS_INTERNAL_ERROR, NULL); + + return component; +} + +/* Count number of columns with data (excluding tags) */ +static +int32_t data_column_count( + ecs_world_t * world, + ecs_table_t * table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + /* Typically all components will be clustered together at the start of + * the type as components are created from a separate id pool, and type + * vectors are sorted. + * Explicitly check for EcsComponent and EcsName since the ecs_has check + * doesn't work during bootstrap. */ + if ((component == ecs_typeid(EcsComponent)) || + (component == ecs_typeid(EcsName)) || + ecs_component_from_id(world, component) != NULL) + { + count = c_ptr_i + 1; + } + }); + + return count; +} + +/* Count number of switch columns */ +static +int32_t switch_column_count( + ecs_table_t *table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, SWITCH)) { + if (!count) { + table->sw_column_offset = c_ptr_i; + } + count ++; + } + }); + + return count; +} + +/* Count number of bitset columns */ +static +int32_t bitset_column_count( + ecs_table_t *table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, DISABLED)) { + if (!count) { + table->bs_column_offset = c_ptr_i; + } + count ++; + } + }); + + return count; +} + +static +ecs_type_t entities_to_type( + ecs_entities_t *entities) +{ + if (entities->count) { + ecs_vector_t *result = NULL; + ecs_vector_set_count(&result, ecs_entity_t, entities->count); + ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t); + ecs_os_memcpy(array, entities->array, ECS_SIZEOF(ecs_entity_t) * entities->count); + return result; + } else { + return NULL; + } +} + +static +void register_child_table( + ecs_world_t * world, + ecs_table_t * table, + ecs_entity_t parent) +{ + /* Register child table with parent */ + ecs_vector_t *child_tables = ecs_map_get_ptr( + world->child_tables, ecs_vector_t*, parent); + if (!child_tables) { + child_tables = ecs_vector_new(ecs_table_t*, 1); + } + + ecs_table_t **el = ecs_vector_add(&child_tables, ecs_table_t*); + *el = table; + + if (!world->child_tables) { + world->child_tables = ecs_map_new(ecs_vector_t*, 1); + } + + ecs_map_set(world->child_tables, parent, &child_tables); +} + +static +ecs_edge_t* get_edge( + ecs_table_t *node, + ecs_entity_t e) +{ + if (e < ECS_HI_COMPONENT_ID) { + if (!node->lo_edges) { + node->lo_edges = ecs_os_calloc(sizeof(ecs_edge_t) * ECS_HI_COMPONENT_ID); + } + return &node->lo_edges[e]; + } else { + if (!node->hi_edges) { + node->hi_edges = ecs_map_new(ecs_edge_t, 1); + } + return ecs_map_ensure(node->hi_edges, ecs_edge_t, e); + } +} + +static +void init_edges( + ecs_world_t * world, + ecs_table_t * table) +{ + ecs_entity_t *entities = ecs_vector_first(table->type, ecs_entity_t); + int32_t count = ecs_vector_count(table->type); + + table->lo_edges = NULL; + table->hi_edges = NULL; + + /* Make add edges to own components point to self */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + ecs_edge_t *edge = get_edge(table, e); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + edge->add = table; + + if (count == 1) { + edge->remove = &world->store.root; + } + + /* As we're iterating over the table components, also set the table + * flags. These allow us to quickly determine if the table contains + * data that needs to be handled in a special way, like prefabs or + * containers */ + if (e <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; + } + + if (e == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + table->flags |= EcsTableIsDisabled; + } + + if (e == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } + + if (e == ecs_typeid(EcsComponent)) { + table->flags |= EcsTableHasComponentData; + } + + if (ECS_HAS_ROLE(e, XOR)) { + table->flags |= EcsTableHasXor; + } + + if (ECS_HAS_ROLE(e, INSTANCEOF)) { + table->flags |= EcsTableHasBase; + } + + if (ECS_HAS_ROLE(e, SWITCH)) { + table->flags |= EcsTableHasSwitch; + } + + if (ECS_HAS_ROLE(e, DISABLED)) { + table->flags |= EcsTableHasDisabled; + } + + if (ECS_HAS_ROLE(e, CHILDOF)) { + ecs_entity_t parent = e & ECS_COMPONENT_MASK; + ecs_assert(!ecs_exists(world, parent) || ecs_is_alive(world, parent), ECS_INTERNAL_ERROR, NULL); + table->flags |= EcsTableHasParent; + register_child_table(world, table, parent); + } + + if (ECS_HAS_ROLE(e, CHILDOF) || ECS_HAS_ROLE(e, INSTANCEOF)) { + ecs_set_watch(world, e & ECS_COMPONENT_MASK); + } + } + + /* Register component info flags for all columns */ + ecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableComponentInfo + }); + + /* Register as root table */ + if (!(table->flags & EcsTableHasParent)) { + register_child_table(world, table, 0); + } +} + +static +void init_table( + ecs_world_t * world, + ecs_table_t * table, + ecs_entities_t * entities) +{ + table->type = entities_to_type(entities); + table->c_info = NULL; + table->data = NULL; + table->flags = 0; + table->dirty_state = NULL; + table->monitors = NULL; + table->on_set = NULL; + table->on_set_all = NULL; + table->on_set_override = NULL; + table->un_set_all = NULL; + table->alloc_count = 0; + + table->queries = NULL; + table->column_count = data_column_count(world, table); + table->sw_column_count = switch_column_count(table); + table->bs_column_count = bitset_column_count(table); + + init_edges(world, table); +} + +static +ecs_table_t *create_table( + ecs_world_t * world, + ecs_entities_t * entities, + uint64_t hash) +{ + ecs_table_t *result = ecs_sparse_add(world->store.tables, ecs_table_t); + result->id = ecs_to_u32(ecs_sparse_last_id(world->store.tables)); + + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + init_table(world, result, entities); + +#ifndef NDEBUG + char *expr = ecs_type_str(world, result->type); + ecs_trace_2("table #[green][%s]#[normal] created", expr); + ecs_os_free(expr); +#endif + ecs_log_push(); + + /* Store table in lookup map */ + ecs_vector_t *tables = ecs_map_get_ptr(world->store.table_map, ecs_vector_t*, hash); + ecs_table_t **elem = ecs_vector_add(&tables, ecs_table_t*); + *elem = result; + ecs_map_set(world->store.table_map, hash, &tables); + + ecs_notify_queries(world, &(ecs_query_event_t) { + .kind = EcsQueryTableMatch, + .table = result + }); + + ecs_log_pop(); + + return result; +} + +static +void add_entity_to_type( + ecs_type_t type, + ecs_entity_t add, + ecs_entity_t replace, + ecs_entities_t *out) +{ + int32_t count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + bool added = false; + + int32_t i, el = 0; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (e == replace) { + continue; + } + + if (e > add && !added) { + out->array[el ++] = add; + added = true; + } + + out->array[el ++] = e; + + ecs_assert(el <= out->count, ECS_INTERNAL_ERROR, NULL); + } + + if (!added) { + out->array[el ++] = add; + } + + out->count = el; + + ecs_assert(out->count != 0, ECS_INTERNAL_ERROR, NULL); +} + +static +void remove_entity_from_type( + ecs_type_t type, + ecs_entity_t remove, + ecs_entities_t *out) +{ + int32_t count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + int32_t i, el = 0; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (e != remove) { + out->array[el ++] = e; + ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); + } + } + + out->count = el; +} + +static +void create_backlink_after_add( + ecs_table_t * next, + ecs_table_t * prev, + ecs_entity_t add) +{ + ecs_edge_t *edge = get_edge(next, add); + if (!edge->remove) { + edge->remove = prev; + } +} + +static +void create_backlink_after_remove( + ecs_table_t * next, + ecs_table_t * prev, + ecs_entity_t add) +{ + ecs_edge_t *edge = get_edge(next, add); + if (!edge->add) { + edge->add = prev; + } +} + +static +ecs_entity_t find_xor_replace( + ecs_world_t * world, + ecs_table_t * table, + ecs_type_t type, + ecs_entity_t add) +{ + if (table->flags & EcsTableHasXor) { + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + int32_t i, type_count = ecs_vector_count(type); + ecs_type_t xor_type = NULL; + + for (i = type_count - 1; i >= 0; i --) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, XOR)) { + ecs_entity_t e_type = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, e_type, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_type_owns_entity( + world, type_ptr->normalized, add, true)) + { + xor_type = type_ptr->normalized; + } + } else if (xor_type) { + if (ecs_type_owns_entity(world, xor_type, e, true)) { + return e; + } + } + } + } + + return 0; +} + +int32_t ecs_table_switch_from_case( + ecs_world_t * world, + ecs_table_t * table, + ecs_entity_t add) +{ + ecs_type_t type = table->type; + ecs_data_t *data = ecs_table_get_data(table); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + int32_t i, count = table->sw_column_count; + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + add = add & ECS_COMPONENT_MASK; + + ecs_sw_column_t *sw_columns = NULL; + + if (data && (sw_columns = data->sw_columns)) { + /* Fast path, we can get the switch type from the column data */ + for (i = 0; i < count; i ++) { + ecs_type_t sw_type = sw_columns[i].type; + if (ecs_type_owns_entity(world, sw_type, add, true)) { + return i; + } + } + } else { + /* Slow path, table is empty, so we'll have to get the switch types by + * actually inspecting the switch type entities. */ + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i + table->sw_column_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; + + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_type_owns_entity( + world, type_ptr->normalized, add, true)) + { + return i; + } + } + } + + /* If a table was not found, this is an invalid switch case */ + ecs_abort(ECS_INVALID_CASE, NULL); + + return -1; +} + +static +ecs_table_t *find_or_create_table_include( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t add) +{ + /* If table has one or more switches and this is a case, return self */ + if (ECS_HAS_ROLE(add, CASE)) { + ecs_assert((node->flags & EcsTableHasSwitch) != 0, + ECS_INVALID_CASE, NULL); + return node; + } else { + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); + + ecs_entities_t entities = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * (count + 1)), + .count = count + 1 + }; + + /* If table has a XOR column, check if the entity that is being added to + * the table is part of the XOR type, and if it is, find the current + * entity in the table type matching the XOR type. This entity must be + * replaced in the new table, to ensure the XOR constraint isn't + * violated. */ + ecs_entity_t replace = find_xor_replace(world, node, type, add); + + add_entity_to_type(type, add, replace, &entities); + + ecs_table_t *result = ecs_table_find_or_create(world, &entities); + + if (result != node) { + create_backlink_after_add(result, node, add); + } + + return result; + } +} + +static +ecs_table_t *find_or_create_table_exclude( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t remove) +{ + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); + + ecs_entities_t entities = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * count), + .count = count + }; + + remove_entity_from_type(type, remove, &entities); + + ecs_table_t *result = ecs_table_find_or_create(world, &entities); + if (!result) { + return NULL; + } + + if (result != node) { + create_backlink_after_remove(result, node, remove); + } + + return result; +} + +ecs_table_t* ecs_table_traverse_remove( + ecs_world_t * world, + ecs_table_t * node, + ecs_entities_t * to_remove, + ecs_entities_t * removed) +{ + int32_t i, count = to_remove->count; + ecs_entity_t *entities = to_remove->array; + node = node ? node : &world->store.root; + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Removing 0 from an entity is not valid */ + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_edge_t *edge = get_edge(node, e); + ecs_table_t *next = edge->remove; + + if (!next) { + if (edge->add == node) { + /* Find table with all components of node except 'e' */ + next = find_or_create_table_exclude(world, node, e); + if (!next) { + return NULL; + } + + edge->remove = next; + } else { + /* If the add edge does not point to self, the table + * does not have the entity in to_remove. */ + continue; + } + } + + bool has_case = ECS_HAS_ROLE(e, CASE); + if (removed && (node != next || has_case)) { + removed->array[removed->count ++] = e; + } + + node = next; + } + + return node; +} + +static +void find_owned_components( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t base, + ecs_entities_t * owned) +{ + /* If we're adding an INSTANCEOF relationship, check if the base + * has OWNED components that need to be added to the instance */ + ecs_type_t t = ecs_get_type(world, base); + + int i, count = ecs_vector_count(t); + ecs_entity_t *entities = ecs_vector_first(t, ecs_entity_t); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + if (ECS_HAS_ROLE(e, INSTANCEOF)) { + find_owned_components(world, node, e & ECS_COMPONENT_MASK, owned); + } else + if (ECS_HAS_ROLE(e, OWNED)) { + e = e & ECS_COMPONENT_MASK; + + /* If entity is a type, add each component in the type */ + const EcsType *t_ptr = ecs_get(world, e, EcsType); + if (t_ptr) { + ecs_type_t n = t_ptr->normalized; + int32_t j, n_count = ecs_vector_count(n); + ecs_entity_t *n_entities = ecs_vector_first(n, ecs_entity_t); + for (j = 0; j < n_count; j ++) { + owned->array[owned->count ++] = n_entities[j]; + } + } else { + owned->array[owned->count ++] = e & ECS_COMPONENT_MASK; + } + } + } +} + +ecs_table_t* ecs_table_traverse_add( + ecs_world_t * world, + ecs_table_t * node, + ecs_entities_t * to_add, + ecs_entities_t * added) +{ + int32_t i, count = to_add->count; + ecs_entity_t *entities = to_add->array; + node = node ? node : &world->store.root; + + ecs_entity_t owned_array[ECS_MAX_ADD_REMOVE]; + ecs_entities_t owned = { + .array = owned_array, + .count = 0 + }; + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Adding 0 to an entity is not valid */ + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_edge_t *edge = get_edge(node, e); + ecs_table_t *next = edge->add; + + if (!next) { + next = find_or_create_table_include(world, node, e); + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + edge->add = next; + } + + bool has_case = ECS_HAS_ROLE(e, CASE); + if (added && (node != next || has_case)) { + added->array[added->count ++] = e; + } + + if ((node != next) && ECS_HAS_ROLE(e, INSTANCEOF)) { + find_owned_components(world, next, ECS_COMPONENT_MASK & e, &owned); + } + + node = next; + } + + /* In case OWNED components were found, add them as well */ + if (owned.count) { + node = ecs_table_traverse_add(world, node, &owned, added); + } + + return node; +} + +static +int ecs_entity_compare( + const void *e1, + const void *e2) +{ + ecs_entity_t v1 = *(ecs_entity_t*)e1; + ecs_entity_t v2 = *(ecs_entity_t*)e2; + if (v1 < v2) { + return -1; + } else if (v1 > v2) { + return 1; + } else { + return 0; + } +} + +static +bool ecs_entity_array_is_ordered( + ecs_entities_t *entities) +{ + ecs_entity_t prev = 0; + ecs_entity_t *array = entities->array; + int32_t i, count = entities->count; + + for (i = 0; i < count; i ++) { + if (!array[i] && !prev) { + continue; + } + if (array[i] <= prev) { + return false; + } + prev = array[i]; + } + + return true; +} + +static +int32_t ecs_entity_array_dedup( + ecs_entity_t *array, + int32_t count) +{ + int32_t j, k; + ecs_entity_t prev = array[0]; + + for (k = j = 1; k < count; j ++, k++) { + ecs_entity_t e = array[k]; + if (e == prev) { + k ++; + } + + array[j] = e; + prev = e; + } + + return count - (k - j); +} + +#ifndef NDEBUG + +static +int32_t count_occurrences( + ecs_world_t * world, + ecs_entities_t * entities, + ecs_entity_t entity, + int32_t constraint_index) +{ + const EcsType *type_ptr = ecs_get(world, entity, EcsType); + ecs_assert(type_ptr != NULL, + ECS_INVALID_PARAMETER, "flag must be applied to type"); + + ecs_type_t type = type_ptr->normalized; + int32_t count = 0; + + int i; + for (i = 0; i < constraint_index; i ++) { + ecs_entity_t e = entities->array[i]; + if (e & ECS_ROLE_MASK) { + break; + } + + if (ecs_type_has_entity(world, type, e)) { + count ++; + } + } + + return count; +} + +static +void verify_constraints( + ecs_world_t * world, + ecs_entities_t * entities) +{ + int i, count = entities->count; + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = entities->array[i]; + ecs_entity_t mask = e & ECS_ROLE_MASK; + if (!mask || + ((mask != ECS_OR) && + (mask != ECS_XOR) && + (mask != ECS_NOT))) + { + break; + } + + ecs_entity_t entity = e & ECS_COMPONENT_MASK; + int32_t matches = count_occurrences(world, entities, entity, i); + switch(mask) { + case ECS_OR: + ecs_assert(matches >= 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + case ECS_XOR: + ecs_assert(matches == 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + case ECS_NOT: + ecs_assert(matches == 0, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + } + } +} + +#endif + +static +ecs_table_t *find_or_create( + ecs_world_t * world, + ecs_entities_t * entities) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Make sure array is ordered and does not contain duplicates */ + int32_t type_count = entities->count; + ecs_entity_t *ordered = NULL; + + if (!type_count) { + return &world->store.root; + } + + if (!ecs_entity_array_is_ordered(entities)) { + ecs_size_t size = ECS_SIZEOF(ecs_entity_t) * type_count; + ordered = ecs_os_alloca(size); + ecs_os_memcpy(ordered, entities->array, size); + qsort( + ordered, (size_t)type_count, sizeof(ecs_entity_t), ecs_entity_compare); + type_count = ecs_entity_array_dedup(ordered, type_count); + } else { + ordered = entities->array; + } + + uint64_t hash = 0; + ecs_hash(ordered, entities->count * ECS_SIZEOF(ecs_entity_t), &hash); + ecs_vector_t *table_vec = ecs_map_get_ptr( + world->store.table_map, ecs_vector_t*, hash); + if (table_vec) { + /* Usually this will be just one, but in the case of a collision + * multiple tables can be stored using the same hash. */ + int32_t i, count = ecs_vector_count(table_vec); + ecs_table_t *table, **tables = ecs_vector_first( + table_vec, ecs_table_t*); + + for (i = 0; i < count; i ++) { + table = tables[i]; + int32_t t, table_type_count = ecs_vector_count(table->type); + + /* If number of components in table doesn't match, it's definitely + * a collision. */ + if (table_type_count != type_count) { + table = NULL; + continue; + } + + /* Check if components of table match */ + ecs_entity_t *table_type = ecs_vector_first( + table->type, ecs_entity_t); + for (t = 0; t < type_count; t ++) { + if (table_type[t] != ordered[t]) { + table = NULL; + break; + } + } + + if (table) { + return table; + } + } + } + + /* If we get here, table needs to be created which is only allowed when the + * application is not currently in progress */ + ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL); + + ecs_entities_t ordered_entities = { + .array = ordered, + .count = type_count + }; + +#ifndef NDEBUG + /* Check for constraint violations */ + verify_constraints(world, &ordered_entities); +#endif + + /* If we get here, the table has not been found, so create it. */ + ecs_table_t *result = create_table(world, &ordered_entities, hash); + + ecs_assert(ordered_entities.count == ecs_vector_count(result->type), + ECS_INTERNAL_ERROR, NULL); + + return result; +} + +ecs_table_t* ecs_table_find_or_create( + ecs_world_t * world, + ecs_entities_t * components) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + return find_or_create(world, components); +} + +ecs_table_t* ecs_table_from_type( + ecs_world_t *world, + ecs_type_t type) +{ + ecs_entities_t components = ecs_type_to_entities(type); + return ecs_table_find_or_create( + world, &components); +} + +void ecs_init_root_table( + ecs_world_t *world) +{ + ecs_entities_t entities = { + .array = NULL, + .count = 0 + }; + + init_table(world, &world->store.root, &entities); +} + +void ecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + + uint32_t i; + + if (table->lo_edges) { + for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) { + ecs_edge_t *e = &table->lo_edges[i]; + ecs_table_t *add = e->add, *remove = e->remove; + if (add) { + add->lo_edges[i].remove = NULL; + } + if (remove) { + remove->lo_edges[i].add = NULL; + } + } + } + + ecs_map_iter_t it = ecs_map_iter(table->hi_edges); + ecs_edge_t *edge; + ecs_map_key_t component; + while ((edge = ecs_map_next(&it, ecs_edge_t, &component))) { + ecs_table_t *add = edge->add, *remove = edge->remove; + if (add) { + ecs_edge_t *e = get_edge(add, component); + e->remove = NULL; + if (!e->add) { + ecs_map_remove(add->hi_edges, component); + } + } + if (remove) { + ecs_edge_t *e = get_edge(remove, component); + e->add = NULL; + if (!e->remove) { + ecs_map_remove(remove->hi_edges, component); + } + } + } +} + +/* The ratio used to determine whether the map should rehash. If + * (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */ +#define LOAD_FACTOR (1.5) +#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) +#define GET_ELEM(array, elem_size, index) \ + ECS_OFFSET(array, (elem_size) * (index)) + +struct ecs_bucket_t { + ecs_map_key_t *keys; /* Array with keys */ + void *payload; /* Payload array */ + int32_t count; /* Number of elements in bucket */ +}; + +struct ecs_map_t { + ecs_bucket_t *buckets; + int32_t elem_size; + int32_t bucket_count; + int32_t count; +}; + +/* Get bucket count for number of elements */ +static +int32_t get_bucket_count( + int32_t element_count) +{ + return ecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); +} + +/* Get bucket index for provided map key */ +static +int32_t get_bucket_id( + int32_t bucket_count, + ecs_map_key_t key) +{ + ecs_assert(bucket_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t result = (int32_t)(key & ((uint64_t)bucket_count - 1)); + ecs_assert(result < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + return result; +} + +/* Get bucket for key */ +static +ecs_bucket_t* get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) +{ + int32_t bucket_count = map->bucket_count; + if (!bucket_count) { + return NULL; + } + + int32_t bucket_id = get_bucket_id(bucket_count, key); + ecs_assert(bucket_id < bucket_count, ECS_INTERNAL_ERROR, NULL); + + return &map->buckets[bucket_id]; +} + +/* Ensure that map has at least new_count buckets */ +static +void ensure_buckets( + ecs_map_t *map, + int32_t new_count) +{ + int32_t bucket_count = map->bucket_count; + new_count = ecs_next_pow_of_2(new_count); + if (new_count && new_count > bucket_count) { + map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t)); + map->bucket_count = new_count; + + ecs_os_memset( + ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)), + 0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t)); + } +} + +/* Free contents of bucket */ +static +void clear_bucket( + ecs_bucket_t *bucket) +{ + ecs_os_free(bucket->keys); + ecs_os_free(bucket->payload); + bucket->keys = NULL; + bucket->payload = NULL; + bucket->count = 0; +} + +/* Clear all buckets */ +static +void clear_buckets( + ecs_map_t *map) +{ + ecs_bucket_t *buckets = map->buckets; + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + clear_bucket(&buckets[i]); + } + ecs_os_free(buckets); + map->buckets = NULL; + map->bucket_count = 0; +} + +/* Find or create bucket for specified key */ +static +ecs_bucket_t* ensure_bucket( + ecs_map_t *map, + ecs_map_key_t key) +{ + if (!map->bucket_count) { + ensure_buckets(map, 2); + } + + int32_t bucket_id = get_bucket_id(map->bucket_count, key); + ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; +} + +/* Add element to bucket */ +static +int32_t add_to_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + int32_t index = bucket->count ++; + int32_t bucket_count = index + 1; + + bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count); + bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count); + bucket->keys[index] = key; + + if (payload) { + void *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_os_memcpy(elem, payload, elem_size); + } + + return index; +} + +/* Remove element from bucket */ +static +void remove_from_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + int32_t index) +{ + (void)key; + + ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_count = -- bucket->count; + + if (index != bucket->count) { + ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL); + bucket->keys[index] = bucket->keys[bucket_count]; + + ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count); + + ecs_os_memcpy(elem, last_elem, elem_size); + } +} + +/* Get payload pointer for key from bucket */ +static +void* get_from_bucket( + ecs_bucket_t *bucket, + ecs_map_key_t key, + ecs_size_t elem_size) +{ + ecs_map_key_t *keys = bucket->keys; + int32_t i, count = bucket->count; + + for (i = 0; i < count; i ++) { + if (keys[i] == key) { + return GET_ELEM(bucket->payload, elem_size, i); + } + } + return NULL; +} + +/* Grow number of buckets */ +static +void rehash( + ecs_map_t *map, + int32_t bucket_count) +{ + ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); + + ecs_size_t elem_size = map->elem_size; + + ensure_buckets(map, bucket_count); + + ecs_bucket_t *buckets = map->buckets; + ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_id; + + /* Iterate backwards as elements could otherwise be moved to existing + * buckets which could temporarily cause the number of elements in a + * bucket to exceed BUCKET_COUNT. */ + for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) { + ecs_bucket_t *bucket = &buckets[bucket_id]; + + int i, count = bucket->count; + ecs_map_key_t *key_array = bucket->keys; + void *payload_array = bucket->payload; + + for (i = 0; i < count; i ++) { + ecs_map_key_t key = key_array[i]; + void *elem = GET_ELEM(payload_array, elem_size, i); + int32_t new_bucket_id = get_bucket_id(bucket_count, key); + + if (new_bucket_id != bucket_id) { + ecs_bucket_t *new_bucket = &buckets[new_bucket_id]; + + add_to_bucket(new_bucket, elem_size, key, elem); + remove_from_bucket(bucket, elem_size, key, i); + + count --; + i --; + } + } + + if (!bucket->count) { + clear_bucket(bucket); + } + } +} + +ecs_map_t* _ecs_map_new( + ecs_size_t elem_size, + ecs_size_t alignment, + int32_t element_count) +{ + (void)alignment; + + ecs_map_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_map_t) * 1); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + int32_t bucket_count = get_bucket_count(element_count); + + result->count = 0; + result->elem_size = elem_size; + + ensure_buckets(result, bucket_count); + + return result; +} + +void ecs_map_free( + ecs_map_t *map) +{ + if (map) { + clear_buckets(map); + ecs_os_free(map); + } +} + +void* _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + (void)elem_size; + + if (!map) { + return NULL; + } + + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return NULL; + } + + return get_from_bucket(bucket, key, elem_size); +} + +void* _ecs_map_get_ptr( + const ecs_map_t *map, + ecs_map_key_t key) +{ + void * ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key); + + if (ptr_ptr) { + return *(void**)ptr_ptr; + } else { + return NULL; + } +} + +void * _ecs_map_ensure( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + void *result = _ecs_map_get(map, elem_size, key); + if (!result) { + result = _ecs_map_set(map, elem_size, key, NULL); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(result, 0, elem_size); + } + + return result; +} + +void* _ecs_map_set( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t *bucket = ensure_bucket(map, key); + ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + + void *elem = get_from_bucket(bucket, key, elem_size); + if (!elem) { + int32_t index = add_to_bucket(bucket, elem_size, key, payload); + + int32_t map_count = ++map->count; + int32_t target_bucket_count = get_bucket_count(map_count); + int32_t map_bucket_count = map->bucket_count; + + if (target_bucket_count > map_bucket_count) { + rehash(map, target_bucket_count); + bucket = ensure_bucket(map, key); + return get_from_bucket(bucket, key, elem_size); + } else { + return GET_ELEM(bucket->payload, elem_size, index); + } + } else { + if (payload) { + ecs_os_memcpy(elem, payload, elem_size); + } + return elem; + } +} + +void ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return; + } + + int32_t i, bucket_count = bucket->count; + for (i = 0; i < bucket_count; i ++) { + if (bucket->keys[i] == key) { + remove_from_bucket(bucket, map->elem_size, key, i); + map->count --; + } + } +} + +int32_t ecs_map_count( + const ecs_map_t *map) +{ + return map ? map->count : 0; +} + +int32_t ecs_map_bucket_count( + const ecs_map_t *map) +{ + return map ? map->bucket_count : 0; +} + +void ecs_map_clear( + ecs_map_t *map) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + clear_buckets(map); + map->count = 0; +} + +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) +{ + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .bucket_index = 0, + .element_index = 0 + }; +} + +void* _ecs_map_next( + ecs_map_iter_t *iter, + ecs_size_t elem_size, + ecs_map_key_t *key_out) +{ + const ecs_map_t *map = iter->map; + if (!map) { + return NULL; + } + + ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t *bucket = iter->bucket; + int32_t element_index = iter->element_index; + elem_size = map->elem_size; + + do { + if (!bucket) { + int32_t bucket_index = iter->bucket_index; + ecs_bucket_t *buckets = map->buckets; + if (bucket_index < map->bucket_count) { + bucket = &buckets[bucket_index]; + iter->bucket = bucket; + + element_index = 0; + iter->element_index = 0; + } else { + return NULL; + } + } + + if (element_index < bucket->count) { + iter->element_index = element_index + 1; + break; + } else { + bucket = NULL; + iter->bucket_index ++; + } + } while (true); + + if (key_out) { + *key_out = bucket->keys[element_index]; + } + + return GET_ELEM(bucket->payload, elem_size, element_index); +} + +void* _ecs_map_next_ptr( + ecs_map_iter_t *iter, + ecs_map_key_t *key_out) +{ + void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); + if (result) { + return *(void**)result; + } else { + return NULL; + } +} + +void ecs_map_grow( + ecs_map_t *map, + int32_t element_count) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t target_count = map->count + element_count; + int32_t bucket_count = get_bucket_count(target_count); + + if (bucket_count > map->bucket_count) { + rehash(map, bucket_count); + } +} + +void ecs_map_set_size( + ecs_map_t *map, + int32_t element_count) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t bucket_count = get_bucket_count(element_count); + + if (bucket_count) { + rehash(map, bucket_count); + } +} + +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + + if (used) { + *used = map->count * map->elem_size; + } + + if (allocd) { + *allocd += ECS_SIZEOF(ecs_map_t); + + int i, bucket_count = map->bucket_count; + for (i = 0; i < bucket_count; i ++) { + ecs_bucket_t *bucket = &map->buckets[i]; + *allocd += KEY_SIZE * bucket->count; + *allocd += map->elem_size * bucket->count; + } + + *allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count; + } +} + +static +void* get_owned_column_ptr( + const ecs_iter_t *it, + ecs_size_t size, + int32_t table_column, + int32_t row) +{ + ecs_assert(it->table_columns != NULL, ECS_INTERNAL_ERROR, NULL); + (void)size; + + ecs_column_t *column = &((ecs_column_t*)it->table_columns)[table_column - 1]; + ecs_assert(column->size != 0, ECS_COLUMN_HAS_NO_DATA, NULL); + ecs_assert(!size || column->size == size, ECS_COLUMN_TYPE_MISMATCH, NULL); + void *buffer = ecs_vector_first_t(column->data, column->size, column->alignment); + return ECS_OFFSET(buffer, column->size * (it->offset + row)); +} + +static +const void* get_shared_column( + const ecs_iter_t *it, + ecs_size_t size, + int32_t table_column) +{ + ecs_ref_t *refs = it->table->references; + ecs_assert(refs != NULL, ECS_INTERNAL_ERROR, NULL); + (void)size; + +#ifndef NDEBUG + if (size) { + ecs_entity_t component_id = ecs_get_typeid( + it->world, refs[-table_column - 1].component); + + const EcsComponent *cdata = ecs_get( + it->world, component_id, EcsComponent); + + ecs_assert(cdata != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cdata->size == size, ECS_COLUMN_TYPE_MISMATCH, + ecs_get_name(it->world, it->system)); + } +#endif + + ecs_ref_t *ref = &refs[-table_column - 1]; + + return (void*)ecs_get_ref_w_entity( + it->world, ref, ref->entity, ref->component); +} + +static +bool get_table_column( + const ecs_iter_t *it, + int32_t column, + int32_t *table_column_out) +{ + ecs_assert(column <= it->column_count, ECS_INVALID_PARAMETER, NULL); + + int32_t table_column = 0; + + if (column != 0) { + ecs_assert(it->table->columns != NULL, ECS_INTERNAL_ERROR, NULL); + + table_column = it->table->columns[column - 1]; + if (!table_column) { + /* column is not set */ + return false; + } + } + + *table_column_out = table_column; + + return true; +} + +static +void* get_column( + const ecs_iter_t *it, + ecs_size_t size, + int32_t column, + int32_t row) +{ + int32_t table_column; + + if (!column) { + return it->entities; + } + + if (!get_table_column(it, column, &table_column)) { + return NULL; + } + + if (table_column < 0) { + return (void*)get_shared_column(it, size, table_column); + } else { + return get_owned_column_ptr(it, size, table_column, row); + } +} + + +/* --- Public API --- */ + +void* ecs_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column) +{ + return get_column(it, ecs_from_size_t(size), column, 0); +} + +void* ecs_element_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column, + int32_t row) +{ + return get_column(it, ecs_from_size_t(size), column, row); +} + +bool ecs_is_owned( + const ecs_iter_t *it, + int32_t column) +{ + int32_t table_column; + + if (!get_table_column(it, column, &table_column)) { + return true; + } + + return table_column >= 0; +} + +bool ecs_is_readonly( + const ecs_iter_t *it, + int32_t column) +{ + ecs_query_t *query = it->query; + + /* If this is not a query iterator, readonly is meaningless */ + ecs_assert(query != NULL, ECS_INVALID_OPERATION, NULL); + (void)query; + + ecs_sig_column_t *column_data = ecs_vector_get( + it->query->sig.columns, ecs_sig_column_t, column - 1); + + return column_data->inout_kind == EcsIn; +} + +ecs_entity_t ecs_column_source( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->table->columns != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_iter_table_t *table = it->table; + int32_t table_column = table->columns[index - 1]; + if (table_column >= 0) { + return 0; + } + + ecs_assert(table->references != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_ref_t *ref = &table->references[-table_column - 1]; + return ref->entity; +} + +ecs_type_t ecs_column_type( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->table->types != NULL, ECS_INTERNAL_ERROR, NULL); + return it->table->types[index - 1]; +} + +ecs_entity_t ecs_column_entity( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->table->components != NULL, ECS_INTERNAL_ERROR, NULL); + return it->table->components[index - 1]; +} + +size_t ecs_column_size( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->table->columns != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t table_column = it->table->columns[index - 1]; + return ecs_table_column_size(it, table_column - 1); +} + +int32_t ecs_column_index_from_name( + const ecs_iter_t *it, + const char *name) +{ + ecs_sig_column_t *column = NULL; + if (it->query) { + int32_t i, count = ecs_vector_count(it->query->sig.columns); + for (i = 0; i < count; i ++) { + column = ecs_vector_get( + it->query->sig.columns, ecs_sig_column_t, i); + if (column->name) { + if (!strcmp(name, column->name)) { + return i + 1; + } + } + } + } + + return 0; +} + +ecs_type_t ecs_iter_type( + const ecs_iter_t *it) +{ + /* If no table is set it means that the iterator isn't pointing to anything + * yet. The most likely cause for this is that the operation is invoked on + * a new iterator for which "next" hasn't been invoked yet, or on an + * iterator that is out of elements. */ + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_table_t *table = it->table->table; + return table->type; +} + +int32_t ecs_table_component_index( + const ecs_iter_t *it, + ecs_entity_t component) +{ + /* See ecs_iter_type */ + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table->table != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_type_index_of(it->table->table->type, component); +} + +void* ecs_table_column( + const ecs_iter_t *it, + int32_t column_index) +{ + /* See ecs_iter_type */ + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table->table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = it->table->table; + ecs_assert(column_index < ecs_vector_count(table->type), + ECS_INVALID_PARAMETER, NULL); + + if (table->column_count <= column_index) { + return NULL; + } + + ecs_column_t *columns = it->table_columns; + ecs_column_t *column = &columns[column_index]; + return ecs_vector_first_t(column->data, column->size, column->alignment); +} + +size_t ecs_table_column_size( + const ecs_iter_t *it, + int32_t column_index) +{ + /* See ecs_iter_type */ + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table->table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = it->table->table; + ecs_assert(column_index < ecs_vector_count(table->type), + ECS_INVALID_PARAMETER, NULL); + + if (table->column_count <= column_index) { + return 0; + } + + ecs_column_t *columns = it->table_columns; + ecs_column_t *column = &columns[column_index]; + + return ecs_to_size_t(column->size); +} + +int8_t ecs_to_i8( + int64_t v) +{ + ecs_assert(v < INT8_MAX, ECS_INTERNAL_ERROR, NULL); + return (int8_t)v; +} + +int16_t ecs_to_i16( + int64_t v) +{ + ecs_assert(v < INT16_MAX, ECS_INTERNAL_ERROR, NULL); + return (int16_t)v; +} + +uint32_t ecs_to_u32( + uint64_t v) +{ + ecs_assert(v < UINT32_MAX, ECS_INTERNAL_ERROR, NULL); + return (uint32_t)v; +} + +size_t ecs_to_size_t( + int64_t size) +{ + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); + return (size_t)size; +} + +ecs_size_t ecs_from_size_t( + size_t size) +{ + ecs_assert(size < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + return (ecs_size_t)size; +} + +int32_t ecs_next_pow_of_2( + int32_t n) +{ + n --; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n ++; + + return n; +} + +/** Convert time to double */ +double ecs_time_to_double( + ecs_time_t t) +{ + double result; + result = t.sec; + return result + (double)t.nanosec / (double)1000000000; +} + +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2) +{ + ecs_time_t result; + + if (t1.nanosec >= t2.nanosec) { + result.nanosec = t1.nanosec - t2.nanosec; + result.sec = t1.sec - t2.sec; + } else { + result.nanosec = t1.nanosec - t2.nanosec + 1000000000; + result.sec = t1.sec - t2.sec - 1; + } + + return result; +} + +void ecs_sleepf( + double t) +{ + if (t > 0) { + int sec = (int)t; + int nsec = (int)((t - sec) * 1000000000); + ecs_os_sleep(sec, nsec); + } +} + +double ecs_time_measure( + ecs_time_t *start) +{ + ecs_time_t stop, temp; + ecs_os_get_time(&stop); + temp = stop; + stop = ecs_time_sub(stop, *start); + *start = temp; + return ecs_time_to_double(stop); +} + +void* ecs_os_memdup( + const void *src, + ecs_size_t size) +{ + if (!src) { + return NULL; + } + + void *dst = ecs_os_malloc(size); + ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_memcpy(dst, src, size); + return dst; +} + +/* + This code was taken from sokol_time.h + + zlib/libpng license + Copyright (c) 2018 Andre Weissflog + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. +*/ + + +static int ecs_os_time_initialized; + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +static double _ecs_os_time_win_freq; +static LARGE_INTEGER _ecs_os_time_win_start; +#elif defined(__APPLE__) && defined(__MACH__) +#include +static mach_timebase_info_data_t _ecs_os_time_osx_timebase; +static uint64_t _ecs_os_time_osx_start; +#else /* anything else, this will need more care for non-Linux platforms */ +#include +static uint64_t _ecs_os_time_posix_start; +#endif + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} +#endif + +void ecs_os_time_setup(void) { + if ( ecs_os_time_initialized) { + return; + } + + ecs_os_time_initialized = 1; + #if defined(_WIN32) + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&_ecs_os_time_win_start); + _ecs_os_time_win_freq = (double)freq.QuadPart / 1000000000.0; + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_ecs_os_time_osx_timebase); + _ecs_os_time_osx_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _ecs_os_time_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +uint64_t ecs_os_time_now(void) { + ecs_assert(ecs_os_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t now; + + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)(qpc_t.QuadPart / _ecs_os_time_win_freq); + #elif defined(__APPLE__) && defined(__MACH__) + now = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec); + #endif + + return now; +} + +void ecs_os_time_sleep( + int32_t sec, + int32_t nanosec) +{ +#ifndef _WIN32 + struct timespec sleepTime; + ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); + + sleepTime.tv_sec = sec; + sleepTime.tv_nsec = nanosec; + if (nanosleep(&sleepTime, NULL)) { + ecs_os_err("nanosleep failed"); + } +#else + HANDLE timer; + LARGE_INTEGER ft; + + ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#endif +} + + +#if defined(_WIN32) + +static ULONG win32_current_resolution; + +void ecs_increase_timer_resolution(bool enable) +{ + HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); + if (!hntdll) { + return; + } + + LONG (__stdcall *pNtSetTimerResolution)( + ULONG desired, BOOLEAN set, ULONG * current); + + pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) + GetProcAddress(hntdll, "NtSetTimerResolution"); + + if(!pNtSetTimerResolution) { + return; + } + + ULONG current, resolution = 10000; /* 1 ms */ + + if (!enable && win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + win32_current_resolution = 0; + return; + } else if (!enable) { + return; + } + + if (resolution == win32_current_resolution) { + return; + } + + if (win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + } + + if (pNtSetTimerResolution(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(pNtSetTimerResolution(resolution, 1, ¤t)) return; + } + + win32_current_resolution = resolution; +} + +#else +void ecs_increase_timer_resolution(bool enable) +{ + (void)enable; + return; +} +#endif + +#ifdef FLECS_PIPELINE + + +/* Worker thread */ +static +void* worker(void *arg) { + ecs_thread_t *thread = arg; + ecs_world_t *world = thread->world; + + /* Start worker thread, increase counter so main thread knows how many + * workers are ready */ + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running ++; + + if (!world->quit_workers) { + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + } + + ecs_os_mutex_unlock(world->sync_mutex); + + while (!world->quit_workers) { + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)thread, 0); + ecs_pipeline_progress( + (ecs_world_t*)thread, + world->pipeline, + world->stats.delta_time); + ecs_set_scope((ecs_world_t*)thread, old_scope); + } + + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running --; + ecs_os_mutex_unlock(world->sync_mutex); + + return NULL; +} + +/* Start threads */ +static +void start_workers( + ecs_world_t *world, + int32_t threads) +{ + ecs_assert(world->workers == NULL, ECS_INTERNAL_ERROR, NULL); + + world->workers = ecs_vector_new(ecs_thread_t, threads); + world->worker_stages = ecs_vector_new(ecs_stage_t, threads); + + int32_t i; + for (i = 0; i < threads; i ++) { + ecs_thread_t *thread = + ecs_vector_add(&world->workers, ecs_thread_t); + + thread->magic = ECS_THREAD_MAGIC; + thread->world = world; + thread->thread = 0; + thread->index = i; + + thread->stage = ecs_vector_add(&world->worker_stages, ecs_stage_t); + ecs_stage_init(world, thread->stage); + thread->stage->id = 2 + i; /* 0 and 1 are reserved for main and temp */ + thread->stage->world = (ecs_world_t*)thread; + + thread->thread = ecs_os_thread_new(worker, thread); + ecs_assert(thread->thread != 0, ECS_THREAD_ERROR, NULL); + } +} + +/* Wait until all workers are running */ +static +void wait_for_workers( + ecs_world_t *world) +{ + int32_t thread_count = ecs_vector_count(world->workers); + bool wait = true; + + do { + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_running == thread_count) { + wait = false; + } + ecs_os_mutex_unlock(world->sync_mutex); + } while (wait); +} + +/* Synchronize worker threads */ +static +void sync_worker( + ecs_world_t *world) +{ + int32_t thread_count = ecs_vector_count(world->workers); + + /* Signal that thread is waiting */ + ecs_os_mutex_lock(world->sync_mutex); + if (++ world->workers_waiting == thread_count) { + /* Only signal main thread when all threads are waiting */ + ecs_os_cond_signal(world->sync_cond); + } + + /* Wait until main thread signals that thread can continue */ + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + ecs_os_mutex_unlock(world->sync_mutex); +} + +/* Wait until all threads are waiting on sync point */ +static +void wait_for_sync( + ecs_world_t *world) +{ + int32_t thread_count = ecs_vector_count(world->workers); + + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_waiting != thread_count) { + ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + } + + /* We should have been signalled unless all workers are waiting on sync */ + ecs_assert(world->workers_waiting == thread_count, + ECS_INTERNAL_ERROR, NULL); + + ecs_os_mutex_unlock(world->sync_mutex); +} + +/* Signal workers that they can start/resume work */ +static +void signal_workers( + ecs_world_t *world) +{ + ecs_os_mutex_lock(world->sync_mutex); + ecs_os_cond_broadcast(world->worker_cond); + ecs_os_mutex_unlock(world->sync_mutex); +} + +/** Stop worker threads */ +static +void ecs_stop_threads( + ecs_world_t *world) +{ + world->quit_workers = true; + signal_workers(world); + + ecs_vector_each(world->workers, ecs_thread_t, thr, { + ecs_os_thread_join(thr->thread); + ecs_stage_deinit(world, thr->stage); + }); + + ecs_vector_free(world->workers); + ecs_vector_free(world->worker_stages); + world->worker_stages = NULL; + world->workers = NULL; + world->quit_workers = false; + + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); +} + +/* -- Private functions -- */ + +void ecs_worker_begin( + ecs_world_t *world) +{ + if (world->magic == ECS_WORLD_MAGIC) { + ecs_staging_begin(world); + } +} + +bool ecs_worker_sync( + ecs_world_t *world) +{ + int32_t build_count = world->stats.pipeline_build_count_total; + + int32_t thread_count = ecs_vector_count(world->workers); + if (!thread_count) { + ecs_staging_end(world); + + ecs_pipeline_update(world, world->pipeline); + + ecs_staging_begin(world); + } else { + sync_worker(world); + } + + return world->stats.pipeline_build_count_total != build_count; +} + +void ecs_worker_end( + ecs_world_t *world) +{ + int32_t thread_count = ecs_vector_count(world->workers); + if (!thread_count) { + ecs_staging_end(world); + } else { + sync_worker(world); + } +} + +void ecs_workers_progress( + ecs_world_t *world) +{ + ecs_entity_t pipeline = world->pipeline; + int32_t thread_count = ecs_vector_count(world->workers); + + ecs_time_t start = {0}; + if (world->measure_frame_time) { + ecs_time_measure(&start); + } + + if (thread_count <= 1) { + ecs_pipeline_begin(world, pipeline); + ecs_entity_t old_scope = ecs_set_scope(world, 0); + ecs_pipeline_progress(world, pipeline, world->stats.delta_time); + ecs_set_scope(world, old_scope); + ecs_pipeline_end(world); + } else { + int32_t i, sync_count = ecs_pipeline_begin(world, pipeline); + + /* Make sure workers are running and ready */ + wait_for_workers(world); + + /* Synchronize n times for each op in the pipeline */ + for (i = 0; i < sync_count; i ++) { + ecs_staging_begin(world); + + /* Signal workers that they should start running systems */ + world->workers_waiting = 0; + signal_workers(world); + + /* Wait until all workers are waiting on sync point */ + wait_for_sync(world); + + /* Merge */ + ecs_staging_end(world); + + int32_t update_count; + if ((update_count = ecs_pipeline_update(world, pipeline))) { + /* The number of operations in the pipeline could have changed + * as result of the merge */ + sync_count = update_count; + } + } + + ecs_pipeline_end(world); + } + + if (world->measure_frame_time) { + world->stats.system_time_total += (FLECS_FLOAT)ecs_time_measure(&start); + } +} + + +/* -- Public functions -- */ + +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + ecs_assert(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + + int32_t thread_count = ecs_vector_count(world->workers); + + if (!world->arg_threads && thread_count != threads) { + /* Stop existing threads */ + if (ecs_vector_count(world->workers)) { + ecs_stop_threads(world); + ecs_os_cond_free(world->worker_cond); + ecs_os_cond_free(world->sync_cond); + ecs_os_mutex_free(world->sync_mutex); + } + + /* Start threads if number of threads > 1 */ + if (threads > 1) { + world->worker_cond = ecs_os_cond_new(); + world->sync_cond = ecs_os_cond_new(); + world->sync_mutex = ecs_os_mutex_new(); + world->stage_count = 2 + threads; + start_workers(world, threads); + } + + /* Iterate tables, make sure the ecs_data_t arrays are large enough */ + ecs_sparse_each(world->store.tables, ecs_table_t, table, { + ecs_table_get_data(table); + }); + } +} + +#endif + +#ifdef FLECS_PIPELINE + + +ECS_TYPE_DECL(EcsPipelineQuery); + +static ECS_CTOR(EcsPipelineQuery, ptr, { + memset(ptr, 0, _size); +}) + +static ECS_DTOR(EcsPipelineQuery, ptr, { + ecs_vector_free(ptr->ops); +}) + +static +int compare_entity( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); +} + +static +int rank_phase( + ecs_world_t *world, + ecs_entity_t rank_component, + ecs_type_t type) +{ + const EcsType *pipeline_type = ecs_get(world, rank_component, EcsType); + ecs_assert(pipeline_type != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Find tag in system that belongs to pipeline */ + ecs_entity_t *sys_comps = ecs_vector_first(type, ecs_entity_t); + int32_t c, t, count = ecs_vector_count(type); + + ecs_entity_t *tags = ecs_vector_first(pipeline_type->normalized, ecs_entity_t); + int32_t tag_count = ecs_vector_count(pipeline_type->normalized); + + ecs_entity_t result = 0; + + for (c = 0; c < count; c ++) { + ecs_entity_t comp = sys_comps[c]; + for (t = 0; t < tag_count; t ++) { + if (comp == tags[t]) { + result = comp; + break; + } + } + if (result) { + break; + } + } + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result < INT_MAX, ECS_INTERNAL_ERROR, NULL); + + return (int)result; +} + +typedef enum ComponentWriteState { + NotWritten = 0, + WriteToMain, + WriteToStage +} ComponentWriteState; + +typedef struct write_state_t { + ecs_map_t *components; + bool wildcard; +} write_state_t; + +static +int32_t get_write_state( + ecs_map_t *write_state, + ecs_entity_t component) +{ + int32_t *ptr = ecs_map_get(write_state, int32_t, component); + if (ptr) { + return *ptr; + } else { + return 0; + } +} + +static +void set_write_state( + write_state_t *write_state, + ecs_entity_t component, + int32_t value) +{ + if (component == EcsWildcard) { + ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL); + write_state->wildcard = true; + } else { + ecs_map_set(write_state->components, component, &value); + } +} + +static +void reset_write_state( + write_state_t *write_state) +{ + ecs_map_clear(write_state->components); + write_state->wildcard = false; +} + +static +bool check_column_component( + ecs_sig_column_t *column, + bool is_active, + ecs_entity_t component, + write_state_t *write_state) +{ + int32_t state = get_write_state(write_state->components, component); + + if ((column->from_kind == EcsFromAny || column->from_kind == EcsFromOwned) + && column->oper_kind != EcsOperNot) + { + switch(column->inout_kind) { + case EcsInOut: + case EcsIn: + if (state == WriteToStage) { + return true; + } else if (write_state->wildcard) { + return true; + } + // fall through + case EcsOut: + if (is_active && column->inout_kind != EcsIn) { + set_write_state(write_state, component, WriteToMain); + } + }; + } else if (column->from_kind == EcsFromEmpty || + column->oper_kind == EcsOperNot) + { + switch(column->inout_kind) { + case EcsInOut: + case EcsOut: + if (is_active) { + set_write_state(write_state, component, WriteToStage); + } + break; + default: + break; + }; + } + + return false; +} + +static +bool check_column( + ecs_sig_column_t *column, + bool is_active, + write_state_t *write_state) +{ + if (column->oper_kind != EcsOperOr) { + return check_column_component( + column, is_active, column->is.component, write_state); + } + + return false; +} + +static +bool build_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline, + EcsPipelineQuery *pq) +{ + (void)pipeline; + + ecs_query_iter(pq->query); + + if (pq->match_count == pq->query->match_count) { + /* No need to rebuild the pipeline */ + return false; + } + + ecs_trace_2("rebuilding pipeline #[green]%s", + ecs_get_name(world, pipeline)); + + world->stats.pipeline_build_count_total ++; + + write_state_t ws = { + .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), + .wildcard = false + }; + + ecs_pipeline_op_t *op = NULL; + ecs_vector_t *ops = NULL; + ecs_query_t *query = pq->build_query; + + if (pq->ops) { + ecs_vector_free(pq->ops); + } + + /* Iterate systems in pipeline, add ops for running / merging */ + ecs_iter_t it = ecs_query_iter(query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_column(&it, EcsSystem, 1); + + int i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *q = sys[i].query; + if (!q) { + continue; + } + + bool needs_merge = false; + bool is_active = !ecs_has_entity( + world, it.entities[i], EcsInactive); + + ecs_vector_each(q->sig.columns, ecs_sig_column_t, column, { + needs_merge |= check_column(column, is_active, &ws); + }); + + if (needs_merge) { + /* After merge all components will be merged, so reset state */ + reset_write_state(&ws); + op = NULL; + + /* Re-evaluate columns to set write flags if system is active. + * If system is inactive, it can't write anything and so it + * should not insert unnecessary merges. */ + needs_merge = false; + if (is_active) { + ecs_vector_each(q->sig.columns, ecs_sig_column_t, column, { + needs_merge |= check_column(column, true, &ws); + }); + } + + /* The component states were just reset, so if we conclude that + * another merge is needed something is wrong. */ + ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); + } + + if (!op) { + op = ecs_vector_add(&ops, ecs_pipeline_op_t); + op->count = 0; + } + + /* Don't increase count for inactive systems, as they are ignored by + * the query used to run the pipeline. */ + if (is_active) { + op->count ++; + } + } + } + + ecs_map_free(ws.components); + + /* Force sort of query as this could increase the match_count */ + pq->match_count = pq->query->match_count; + pq->ops = ops; + + return true; +} + +static +int32_t iter_reset( + const EcsPipelineQuery *pq, + ecs_iter_t *iter_out, + ecs_pipeline_op_t **op_out, + ecs_entity_t move_to) +{ + ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); + int32_t ran_since_merge = 0; + + ecs_iter_t it = ecs_query_iter(pq->query); + while (ecs_query_next(&it)) { + int32_t i; + for(i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + + ran_since_merge ++; + if (ran_since_merge == op->count) { + ran_since_merge = 0; + op ++; + } + + if (e == move_to) { + *iter_out = it; + *op_out = op; + return i; + } + } + } + + ecs_abort(ECS_UNSUPPORTED, NULL); + + return -1; +} + +int32_t ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + EcsPipelineQuery *pq = ecs_get_mut(world, pipeline, EcsPipelineQuery, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + if (build_pipeline(world, pipeline, pq)) { + return ecs_vector_count(pq->ops); + } else { + return 0; + } +} + +int32_t ecs_pipeline_begin( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL); + + ecs_eval_component_monitors(world); + + EcsPipelineQuery *pq = ecs_get_mut( + world, pipeline, EcsPipelineQuery, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + build_pipeline(world, pipeline, pq); + + return ecs_vector_count(pq->ops); +} + +void ecs_pipeline_end( + ecs_world_t *world) +{ + (void)world; +} + +void ecs_pipeline_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time) +{ + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + int32_t ran_since_merge = 0; + + ecs_worker_begin(world); + ecs_stage_t *stage = ecs_get_stage(&world); + + ecs_iter_t it = ecs_query_iter(pq->query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_column(&it, EcsSystem, 1); + + int32_t i; + for(i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + + ecs_run_intern(world, stage, e, &sys[i], delta_time, 0, 0, + NULL, NULL, false); + + ran_since_merge ++; + world->stats.systems_ran_frame ++; + + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + + /* If the set of matched systems changed as a result of the + * merge, we have to reset the iterator and move it to our + * current position (system). If there are a lot of systems + * in the pipeline this can be an expensive operation, but + * should happen infrequently. */ + if (ecs_worker_sync(world)) { + i = iter_reset(pq, &it, &op, e); + op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); + sys = ecs_column(&it, EcsSystem, 1); + } + } + } + } + + ecs_worker_end(world); +} + +static +void add_pipeline_tags_to_sig( + ecs_world_t *world, + ecs_sig_t *sig, + ecs_type_t type) +{ + (void)world; + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + for (i = 0; i < count; i ++) { + if (!i) { + ecs_sig_add(world, sig, EcsFromAny, EcsOperAnd, EcsIn, entities[i], + 0, NULL); + } else { + ecs_sig_add(world, sig, EcsFromAny, EcsOperOr, EcsIn, entities[i], + 0, NULL); + } + } +} + +static +void EcsOnAddPipeline( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + ecs_entity_t *entities = it->entities; + + int32_t i; + for (i = it->count - 1; i >= 0; i --) { + ecs_entity_t pipeline = entities[i]; + ecs_sig_t sig = { 0 }; + + const EcsType *type_ptr = ecs_get(world, pipeline, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + +#ifndef NDEBUG + char *str = ecs_type_str(world, type_ptr->normalized); + ecs_trace_1("pipeline #[green]%s#[normal] created with #[red][%s]", + ecs_get_name(world, pipeline), str); + ecs_os_free(str); +#endif + ecs_log_push(); + + /* Build signature for pipeline quey that matches EcsSystems, has the + * pipeline as a XOR column, and ignores systems with EcsInactive and + * EcsDisabledIntern. Note that EcsDisabled is automatically ignored by + * the regular query matching */ + ecs_sig_add(world, &sig, EcsFromAny, EcsOperAnd, EcsIn, + ecs_typeid(EcsSystem), 0, NULL); + ecs_sig_add(world, &sig, EcsFromAny, EcsOperNot, EcsIn, EcsInactive, 0, NULL); + ecs_sig_add(world, &sig, EcsFromAny, EcsOperNot, EcsIn, + EcsDisabledIntern, 0, NULL); + add_pipeline_tags_to_sig(world, &sig, type_ptr->normalized); + + /* Create the query. Sort the query by system id and phase */ + ecs_query_t *query = ecs_query_new_w_sig(world, 0, &sig); + ecs_query_order_by(world, query, 0, compare_entity); + ecs_query_group_by(world, query, pipeline, rank_phase); + + /* Build signature for pipeline build query. The build query includes + * systems that are inactive, as an inactive system may become active as + * a result of another system, and as a result the correct merge + * operations need to be put in place. */ + ecs_sig_add(world, &sig, EcsFromAny, EcsOperAnd, EcsIn, + ecs_typeid(EcsSystem), 0, NULL); + ecs_sig_add(world, &sig, EcsFromAny, EcsOperNot, EcsIn, + EcsDisabledIntern, 0, NULL); + add_pipeline_tags_to_sig(world, &sig, type_ptr->normalized); + + /* Use the same sorting functions for the build query */ + ecs_query_t *build_query = ecs_query_new_w_sig(world, 0, &sig); + ecs_query_order_by(world, build_query, 0, compare_entity); + ecs_query_group_by(world, build_query, pipeline, rank_phase); + + EcsPipelineQuery *pq = ecs_get_mut( + world, pipeline, EcsPipelineQuery, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + + pq->query = query; + pq->build_query = build_query; + pq->match_count = -1; + pq->ops = NULL; + + ecs_log_pop(); + } +} + +/* -- Public API -- */ + +bool ecs_progress( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + ecs_frame_begin(world, user_delta_time); + + ecs_workers_progress(world); + + ecs_frame_end(world); + + return !world->should_quit; +} + +void ecs_set_time_scale( + ecs_world_t *world, + FLECS_FLOAT scale) +{ + world->stats.time_scale = scale; +} + +void ecs_reset_clock( + ecs_world_t *world) +{ + world->stats.world_time_total = 0; + world->stats.world_time_total_raw = 0; +} + +void ecs_quit( + ecs_world_t *world) +{ + ecs_get_stage(&world); + world->should_quit = true; +} + +void ecs_deactivate_systems( + ecs_world_t *world) +{ + ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t pipeline = world->pipeline; + const EcsPipelineQuery *pq = ecs_get( world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Iterate over all systems, add EcsInvalid tag if queries aren't matched + * with any tables */ + ecs_iter_t it = ecs_query_iter(pq->build_query); + + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + ecs_defer_none(world, &world->stage); + + while( ecs_query_next(&it)) { + EcsSystem *sys = ecs_column(&it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *query = sys[i].query; + if (query) { + if (!ecs_vector_count(query->tables)) { + ecs_add_entity(world, it.entities[i], EcsInactive); + } + } + } + } + + ecs_defer_flush(world, &world->stage); +} + +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + ecs_assert( ecs_get(world, pipeline, EcsPipelineQuery) != NULL, + ECS_INVALID_PARAMETER, NULL); + + world->pipeline = pipeline; +} + +ecs_entity_t ecs_get_pipeline( + ecs_world_t *world) +{ + return world->pipeline; +} + +ecs_entity_t ecs_new_pipeline( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + const char *expr) +{ + assert(world->magic == ECS_WORLD_MAGIC); + + ecs_entity_t result = ecs_new_type(world, e, name, expr); + ecs_assert(ecs_get(world, result, EcsType) != NULL, + ECS_INTERNAL_ERROR, NULL); + + ecs_add_entity(world, result, EcsPipeline); + + return result; +} + +/* -- Module implementation -- */ + +static +void FlecsPipelineFini( + ecs_world_t *world, + void *ctx) +{ + (void)ctx; + if (world->workers) { + ecs_set_threads(world, 0); + } +} + +void FlecsPipelineImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsPipeline); + + ECS_IMPORT(world, FlecsSystem); + + ecs_set_name_prefix(world, "Ecs"); + + ecs_bootstrap_tag(world, EcsPipeline); + ecs_bootstrap_component(world, EcsPipelineQuery); + + /* Phases of the builtin pipeline are regular entities. Names are set so + * they can be resolved by type expressions. */ + ecs_bootstrap_tag(world, EcsPreFrame); + ecs_bootstrap_tag(world, EcsOnLoad); + ecs_bootstrap_tag(world, EcsPostLoad); + ecs_bootstrap_tag(world, EcsPreUpdate); + ecs_bootstrap_tag(world, EcsOnUpdate); + ecs_bootstrap_tag(world, EcsOnValidate); + ecs_bootstrap_tag(world, EcsPostUpdate); + ecs_bootstrap_tag(world, EcsPreStore); + ecs_bootstrap_tag(world, EcsOnStore); + ecs_bootstrap_tag(world, EcsPostFrame); + + ECS_TYPE_IMPL(EcsPipelineQuery); + + /* Set ctor and dtor for PipelineQuery */ + ecs_set(world, ecs_typeid(EcsPipelineQuery), EcsComponentLifecycle, { + .ctor = ecs_ctor(EcsPipelineQuery), + .dtor = ecs_dtor(EcsPipelineQuery) + }); + + /* When the Pipeline tag is added a pipeline will be created */ + ECS_TRIGGER(world, EcsOnAddPipeline, EcsOnAdd, Pipeline); + + /* Create the builtin pipeline */ + world->pipeline = ecs_new_pipeline(world, 0, "BuiltinPipeline", + "PreFrame, OnLoad, PostLoad, PreUpdate, OnUpdate," + " OnValidate, PostUpdate, PreStore, OnStore, PostFrame"); + + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); +} + +#endif + +#ifdef FLECS_TIMER + + +ecs_type_t ecs_type(EcsTimer); +ecs_type_t ecs_type(EcsRateFilter); + +static +void AddTickSource(ecs_iter_t *it) { + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], EcsTickSource, {0}); + } +} + +static +void ProgressTimers(ecs_iter_t *it) { + EcsTimer *timer = ecs_column(it, EcsTimer, 1); + EcsTickSource *tick_source = ecs_column(it, EcsTickSource, 2); + + ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); + + int i; + for (i = 0; i < it->count; i ++) { + tick_source[i].tick = false; + + if (!timer[i].active) { + continue; + } + + FLECS_FLOAT time_elapsed = timer[i].time + it->world->stats.delta_time_raw; + FLECS_FLOAT timeout = timer[i].timeout; + + if (time_elapsed >= timeout) { + FLECS_FLOAT t = time_elapsed - timeout; + if (t > timeout) { + t = 0; + } + + timer[i].time = t; /* Initialize with remainder */ + tick_source[i].tick = true; + tick_source[i].time_elapsed = time_elapsed; + + if (timer[i].single_shot) { + timer[i].active = false; + } + } else { + timer[i].time = time_elapsed; + } + } +} + +static +void ProgressRateFilters(ecs_iter_t *it) { + EcsRateFilter *filter = ecs_column(it, EcsRateFilter, 1); + EcsTickSource *tick_dst = ecs_column(it, EcsTickSource, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t src = filter[i].src; + bool inc = false; + + filter[i].time_elapsed += it->delta_time; + + if (src) { + const EcsTickSource *tick_src = ecs_get(it->world, src, EcsTickSource); + if (tick_src) { + inc = tick_src->tick; + } + } else { + inc = true; + } + + if (inc) { + filter[i].tick_count ++; + bool triggered = !(filter[i].tick_count % filter[i].rate); + tick_dst[i].tick = triggered; + tick_dst[i].time_elapsed = filter[i].time_elapsed; + + if (triggered) { + filter[i].time_elapsed = 0; + } + } else { + tick_dst[i].tick = false; + } + } +} + +ecs_entity_t ecs_set_timeout( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT timeout) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = timeout, + .single_shot = true, + .active = true + }); + + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; + } + + return timer; +} + +FLECS_FLOAT ecs_get_timeout( + ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(timer != 0, ECS_INVALID_PARAMETER, NULL); + + EcsTimer *value = ecs_get_mut(world, timer, EcsTimer, NULL); + if (value) { + return value->timeout; + } else { + return 0; + } +} + +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT interval) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = interval, + .active = true + }); + + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; + } + + return timer; +} + +FLECS_FLOAT ecs_get_interval( + ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + return 0; + } + + EcsTimer *value = ecs_get_mut(world, timer, EcsTimer, NULL); + if (value) { + return value->timeout; + } else { + return 0; + } +} + +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ptr->active = true; + ptr->time = 0; +} + +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ptr->active = false; +} + +ecs_entity_t ecs_set_rate_filter( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + filter = ecs_set(world, filter, EcsRateFilter, { + .rate = rate, + .src = source + }); + + EcsSystem *system_data = ecs_get_mut(world, filter, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = filter; + } + + return filter; +} + +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(tick_source != 0, ECS_INVALID_PARAMETER, NULL); + + EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + system_data->tick_source = tick_source; +} + +void FlecsTimerImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsTimer); + + ECS_IMPORT(world, FlecsPipeline); + + ecs_set_name_prefix(world, "Ecs"); + + ecs_bootstrap_component(world, EcsTimer); + ecs_bootstrap_component(world, EcsRateFilter); + + /* Add EcsTickSource to timers and rate filters */ + ECS_SYSTEM(world, AddTickSource, EcsPreFrame, [in] Timer || RateFilter, [out] !flecs.system.TickSource); + + /* Timer handling */ + ECS_SYSTEM(world, ProgressTimers, EcsPreFrame, Timer, flecs.system.TickSource); + + /* Rate filter handling */ + ECS_SYSTEM(world, ProgressRateFilters, EcsPreFrame, [in] RateFilter, [out] flecs.system.TickSource); +} + +#endif + +#ifdef FLECS_SYSTEM + + +/* Global type variables */ +ECS_TYPE_DECL(EcsComponentLifecycle); +ECS_TYPE_DECL(EcsTrigger); +ECS_TYPE_DECL(EcsSystem); +ECS_TYPE_DECL(EcsTickSource); +ECS_TYPE_DECL(EcsSignatureExpr); +ECS_TYPE_DECL(EcsSignature); +ECS_TYPE_DECL(EcsQuery); +ECS_TYPE_DECL(EcsIterAction); +ECS_TYPE_DECL(EcsContext); + +static +ecs_on_demand_in_t* get_in_component( + ecs_map_t *component_map, + ecs_entity_t component) +{ + ecs_on_demand_in_t *in = ecs_map_get( + component_map, ecs_on_demand_in_t, component); + if (!in) { + ecs_on_demand_in_t in_value = {0}; + ecs_map_set(component_map, component, &in_value); + in = ecs_map_get(component_map, ecs_on_demand_in_t, component); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + } + + return in; +} + +static +void activate_in_columns( + ecs_world_t *world, + ecs_query_t *query, + ecs_map_t *component_map, + bool activate) +{ + ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t); + int32_t i, count = ecs_vector_count(query->sig.columns); + + for (i = 0; i < count; i ++) { + if (columns[i].inout_kind == EcsIn) { + ecs_on_demand_in_t *in = get_in_component( + component_map, columns[i].is.component); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + + in->count += activate ? 1 : -1; + + ecs_assert(in->count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* If this is the first system that registers the in component, walk + * over all already registered systems to enable them */ + if (in->systems && + ((activate && in->count == 1) || + (!activate && !in->count))) + { + ecs_on_demand_out_t **out = ecs_vector_first( + in->systems, ecs_on_demand_out_t*); + int32_t s, in_count = ecs_vector_count(in->systems); + + for (s = 0; s < in_count; s ++) { + /* Increase the count of the system with the out params */ + out[s]->count += activate ? 1 : -1; + + /* If this is the first out column that is requested from + * the OnDemand system, enable it */ + if (activate && out[s]->count == 1) { + ecs_remove_entity(world, out[s]->system, EcsDisabledIntern); + } else if (!activate && !out[s]->count) { + ecs_add_entity(world, out[s]->system, EcsDisabledIntern); + } + } + } + } + } +} + +static +void register_out_column( + ecs_map_t *component_map, + ecs_entity_t component, + ecs_on_demand_out_t *on_demand_out) +{ + ecs_on_demand_in_t *in = get_in_component(component_map, component); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + + on_demand_out->count += in->count; + ecs_on_demand_out_t **elem = ecs_vector_add(&in->systems, ecs_on_demand_out_t*); + *elem = on_demand_out; +} + +static +void register_out_columns( + ecs_world_t *world, + ecs_entity_t system, + EcsSystem *system_data) +{ + ecs_query_t *query = system_data->query; + ecs_sig_column_t *columns = ecs_vector_first(query->sig.columns, ecs_sig_column_t); + int32_t i, out_count = 0, count = ecs_vector_count(query->sig.columns); + + for (i = 0; i < count; i ++) { + if (columns[i].inout_kind == EcsOut) { + if (!system_data->on_demand) { + system_data->on_demand = ecs_os_malloc(sizeof(ecs_on_demand_out_t)); + ecs_assert(system_data->on_demand != NULL, ECS_OUT_OF_MEMORY, NULL); + + system_data->on_demand->system = system; + system_data->on_demand->count = 0; + } + + /* If column operator is NOT and the inout kind is [out], the system + * explicitly states that it will create the component (it is not + * there, yet it is an out column). In this case it doesn't make + * sense to wait until [in] columns get activated (matched with + * entities) since the component is not there yet. Therefore add it + * to the on_enable_components list, so this system will be enabled + * when a [in] column is enabled, rather than activated */ + ecs_map_t *component_map; + if (columns[i].oper_kind == EcsOperNot) { + component_map = world->on_enable_components; + } else { + component_map = world->on_activate_components; + } + + register_out_column( + component_map, columns[i].is.component, + system_data->on_demand); + + out_count ++; + } + } + + /* If there are no out columns in the on-demand system, the system will + * never be enabled */ + ecs_assert(out_count != 0, ECS_NO_OUT_COLUMNS, ecs_get_name(world, system)); +} + +static +void invoke_status_action( + ecs_world_t *world, + ecs_entity_t system, + const EcsSystem *system_data, + ecs_system_status_t status) +{ + ecs_system_status_action_t action = system_data->status_action; + if (action) { + action(world, system, status, system_data->status_ctx); + } +} + +/* Invoked when system becomes active or inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const EcsSystem *system_data) +{ + ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL); + + if (activate) { + ecs_remove_entity(world, system, EcsInactive); + } + + if (!system_data) { + system_data = ecs_get(world, system, EcsSystem); + } + if (!system_data || !system_data->query) { + return; + } + + /* If system contains in columns, signal that they are now in use */ + activate_in_columns( + world, system_data->query, world->on_activate_components, activate); + + /* Invoke system status action */ + invoke_status_action(world, system, system_data, + activate ? EcsSystemActivated : EcsSystemDeactivated); + + ecs_trace_2("system #[green]%s#[reset] %s", + ecs_get_name(world, system), + activate ? "activated" : "deactivated"); +} + +/* Actually enable or disable system */ +static +void ecs_enable_system( + ecs_world_t *world, + ecs_entity_t system, + EcsSystem *system_data, + bool enabled) +{ + ecs_assert(!world->in_progress, ECS_INTERNAL_ERROR, NULL); + + ecs_query_t *query = system_data->query; + if (!query) { + return; + } + + if (ecs_vector_count(query->tables)) { + /* Only (de)activate system if it has non-empty tables. */ + ecs_system_activate(world, system, enabled, system_data); + system_data = ecs_get_mut(world, system, EcsSystem, NULL); + } + + /* Enable/disable systems that trigger on [in] enablement */ + activate_in_columns( + world, + query, + world->on_enable_components, + enabled); + + /* Invoke action for enable/disable status */ + invoke_status_action( + world, system, system_data, + enabled ? EcsSystemEnabled : EcsSystemDisabled); +} + +static +void ecs_init_system( + ecs_world_t *world, + ecs_entity_t system, + ecs_iter_action_t action, + ecs_query_t *query, + void *ctx) +{ + ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL); + + /* Add & initialize the EcsSystem component */ + bool is_added = false; + EcsSystem *sptr = ecs_get_mut(world, system, EcsSystem, &is_added); + ecs_assert(sptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!is_added) { + ecs_assert(sptr->query == query, ECS_INVALID_PARAMETER, NULL); + } else { + memset(sptr, 0, sizeof(EcsSystem)); + sptr->query = query; + sptr->entity = system; + sptr->tick_source = 0; + sptr->time_spent = 0; + } + + /* Sanity check to make sure creating the query didn't add any additional + * tags or components to the system */ + sptr->action = action; + sptr->ctx = ctx; + + /* Only run this code when the system is created for the first time */ + if (is_added) { + /* If tables have been matched with this system it is active, and we + * should activate the in-columns, if any. This will ensure that any + * OnDemand systems get enabled. */ + if (ecs_vector_count(query->tables)) { + ecs_system_activate(world, system, true, sptr); + } else { + /* If system isn't matched with any tables, mark it as inactive. This + * causes it to be ignored by the main loop. When the system matches + * with a table it will be activated. */ + ecs_add_entity(world, system, EcsInactive); + } + + /* If system is enabled, trigger enable components */ + activate_in_columns(world, query, world->on_enable_components, true); + + /* Check if all non-table column constraints are met. If not, disable + * system (system will be enabled once constraints are met) */ + if (!ecs_sig_check_constraints(world, &query->sig)) { + ecs_add_entity(world, system, EcsDisabledIntern); + } + + /* If the query has a OnDemand system tag, register its [out] columns */ + if (ecs_has_entity(world, system, EcsOnDemand)) { + register_out_columns(world, system, sptr); + ecs_assert(sptr->on_demand != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If there are no systems currently interested in any of the [out] + * columns of the on demand system, disable it */ + if (!sptr->on_demand->count) { + ecs_add_entity(world, system, EcsDisabledIntern); + } + } + + /* Check if system has out columns */ + int32_t i, count = ecs_vector_count(query->sig.columns); + ecs_sig_column_t *columns = ecs_vector_first( + query->sig.columns, ecs_sig_column_t); + + for (i = 0; i < count; i ++) { + if (columns[i].inout_kind != EcsIn) { + break; + } + } + } + + ecs_trace_1("system #[green]%s#[reset] created with #[red]%s", + ecs_get_name(world, system), query->sig.expr); +} + +/* -- Public API -- */ + +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled) +{ + assert(world->magic == ECS_WORLD_MAGIC); + + const EcsType *type_ptr = ecs_get( world, entity, EcsType); + if (type_ptr) { + /* If entity is a type, disable all entities in the type */ + ecs_vector_each(type_ptr->normalized, ecs_entity_t, e, { + ecs_enable(world, *e, enabled); + }); + } else { + if (enabled) { + ecs_remove_entity(world, entity, EcsDisabled); + } else { + ecs_add_entity(world, entity, EcsDisabled); + } + } +} + +void ecs_set_system_status_action( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_status_action_t action, + const void *ctx) +{ + EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + system_data->status_action = action; + system_data->status_ctx = (void*)ctx; + + if (!ecs_has_entity(world, system, EcsDisabled)) { + /* If system is already enabled, generate enable status. The API + * should guarantee that it exactly matches enable-disable + * notifications and activate-deactivate notifications. */ + invoke_status_action(world, system, system_data, EcsSystemEnabled); + + /* If column system has active (non-empty) tables, also generate the + * activate status. */ + if (ecs_vector_count(system_data->query->tables)) { + invoke_status_action( + world, system, system_data, EcsSystemActivated); + } + } +} + +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param, + bool ran_by_app) +{ + if (!param) { + param = system_data->ctx; + } + + FLECS_FLOAT time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; + + if (tick_source) { + const EcsTickSource *tick = ecs_get( + world, tick_source, EcsTickSource); + + if (tick) { + time_elapsed = tick->time_elapsed; + + /* If timer hasn't fired we shouldn't run the system */ + if (!tick->tick) { + return 0; + } + } else { + /* If a timer has been set but the timer entity does not have the + * EcsTimer component, don't run the system. This can be the result + * of a single-shot timer that has fired already. Not resetting the + * timer field of the system will ensure that the system won't be + * ran after the timer has fired. */ + return 0; + } + } + + ecs_time_t time_start; + bool measure_time = world->measure_system_time; + if (measure_time) { + ecs_os_get_time(&time_start); + } + +#ifndef NDEBUG + stage->system = system; + stage->system_columns = system_data->query->sig.columns; +#endif + + bool defer = false; + if (!stage->defer) { + ecs_defer_begin(stage->world); + defer = true; + } + + /* Prepare the query iterator */ + ecs_iter_t it = ecs_query_iter_page(system_data->query, offset, limit); + it.world = stage->world; + it.system = system; + it.delta_time = delta_time; + it.delta_system_time = time_elapsed; + it.world_time = world->stats.world_time_total; + it.frame_offset = offset; + + /* Set param if provided, otherwise use system context */ + if (param) { + it.param = param; + } else { + it.param = system_data->ctx; + } + + ecs_iter_action_t action = system_data->action; + + /* If no filter is provided, just iterate tables & invoke action */ + if (ran_by_app || world == stage->world) { + while (ecs_query_next_w_filter(&it, filter)) { + action(&it); + } + } else { + ecs_thread_t *thread = (ecs_thread_t*)stage->world; + int32_t total = ecs_vector_count(world->workers); + int32_t current = thread->index; + + while (ecs_query_next_worker(&it, current, total)) { + action(&it); + } + } + + if (defer) { + ecs_defer_end(stage->world); + } + + if (measure_time) { + system_data->time_spent += (FLECS_FLOAT)ecs_time_measure(&time_start); + } + +#ifndef NDEBUG + stage->system = 0; + stage->system_columns = NULL; +#endif + + system_data->invoke_count ++; + + return it.interrupted_by; +} + +/* -- Public API -- */ + +ecs_entity_t ecs_run_w_filter( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + bool in_progress = ecs_staging_begin(world); + + EcsSystem *system_data = (EcsSystem*)ecs_get( + world, system, EcsSystem); + assert(system_data != NULL); + + ecs_entity_t interrupted_by = ecs_run_intern( + world, stage, system, system_data, delta_time, offset, limit, + filter, param, true); + + /* If world wasn't in progress when we entered this function, we need to + * merge and reset the in_progress value */ + if (!in_progress) { + ecs_staging_end(world); + } + + return interrupted_by; +} + +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + void *param) +{ + return ecs_run_w_filter(world, system, delta_time, 0, 0, NULL, param); +} + +void ecs_run_monitor( + ecs_world_t *world, + ecs_matched_query_t *monitor, + ecs_entities_t *components, + int32_t row, + int32_t count, + ecs_entity_t *entities) +{ + ecs_query_t *query = monitor->query; + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t system = query->system; + const EcsSystem *system_data = ecs_get(world, system, EcsSystem); + ecs_assert(system_data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!system_data->action) { + return; + } + + ecs_iter_t it = {0}; + ecs_query_set_iter( world, query, &it, + monitor->matched_table_index, row, count); + + it.world = world; + it.triggered_by = components; + it.param = system_data->ctx; + + if (entities) { + it.entities = entities; + } + + it.system = system; + system_data->action(&it); +} + +/* Generic constructor to initialize a component to 0 */ +static +void sys_ctor_init_zero( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)entities; + (void)ctx; + memset(ptr, 0, size * (size_t)count); +} + +/* System destructor */ +static +void ecs_colsystem_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)component; + (void)ctx; + (void)size; + + EcsSystem *system_data = ptr; + + int i; + for (i = 0; i < count; i ++) { + EcsSystem *cur = &system_data[i]; + ecs_entity_t e = entities[i]; + + /* Invoke Deactivated action for active systems */ + if (cur->query && ecs_vector_count(cur->query->tables)) { + invoke_status_action(world, e, ptr, EcsSystemDeactivated); + } + + /* Invoke Disabled action for enabled systems */ + if (!ecs_has_entity(world, e, EcsDisabled) && + !ecs_has_entity(world, e, EcsDisabledIntern)) + { + invoke_status_action(world, e, ptr, EcsSystemDisabled); + } + + ecs_os_free(cur->on_demand); + } +} + +/* Register a trigger for a component */ +static +EcsTrigger* trigger_find_or_create( + ecs_vector_t **triggers, + ecs_entity_t entity) +{ + ecs_vector_each(*triggers, EcsTrigger, trigger, { + if (trigger->self == entity) { + return trigger; + } + }); + + EcsTrigger *result = ecs_vector_add(triggers, EcsTrigger); + return result; +} + +static +void trigger_set( + ecs_world_t *world, + const ecs_entity_t *entities, + EcsTrigger *ct, + int32_t count) +{ + EcsTrigger *el = NULL; + + int i; + for (i = 0; i < count; i ++) { + ecs_entity_t c = ct[i].component; + ecs_c_info_t *c_info = ecs_get_or_create_c_info(world, c); + + switch(ct[i].kind) { + case EcsOnAdd: + el = trigger_find_or_create(&c_info->on_add, entities[i]); + break; + case EcsOnRemove: + el = trigger_find_or_create(&c_info->on_remove, entities[i]); + break; + default: + ecs_abort(ECS_INVALID_PARAMETER, NULL); + break; + } + + ecs_assert(el != NULL, ECS_INTERNAL_ERROR, NULL); + + *el = ct[i]; + el->self = entities[i]; + + ecs_notify_tables(world, &(ecs_table_event_t) { + .kind = EcsTableComponentInfo, + .component = c + }); + + ecs_trace_1("trigger #[green]%s#[normal] created for component #[red]%s", + ct[i].kind == EcsOnAdd + ? "OnAdd" + : "OnRemove", ecs_get_name(world, c)); + } +} + +static +void OnSetTrigger( + ecs_iter_t *it) +{ + EcsTrigger *ct = ecs_column(it, EcsTrigger, 1); + + trigger_set(it->world, it->entities, ct, it->count); +} + +static +void OnSetTriggerCtx( + ecs_iter_t *it) +{ + EcsTrigger *ct = ecs_column(it, EcsTrigger, 1); + EcsContext *ctx = ecs_column(it, EcsContext, 2); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ct[i].ctx = (void*)ctx[i].ctx; + } + + trigger_set(it->world, it->entities, ct, it->count); +} + +/* System that registers component lifecycle callbacks */ +static +void OnSetComponentLifecycle( + ecs_iter_t *it) +{ + EcsComponentLifecycle *cl = ecs_column(it, EcsComponentLifecycle, 1); + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_set_component_actions_w_entity(world, e, &cl[i]); + } +} + +/* Disable system when EcsDisabled is added */ +static +void DisableSystem( + ecs_iter_t *it) +{ + EcsSystem *system_data = ecs_column(it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_enable_system( + it->world, it->entities[i], &system_data[i], false); + } +} + +/* Enable system when EcsDisabled is removed */ +static +void EnableSystem( + ecs_iter_t *it) +{ + EcsSystem *system_data = ecs_column(it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_enable_system( + it->world, it->entities[i], &system_data[i], true); + } +} + +/* Parse a signature expression into the ecs_sig_t data structure */ +static +void CreateSignature( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + ecs_entity_t *entities = it->entities; + + EcsSignatureExpr *signature = ecs_column(it, EcsSignatureExpr, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = entities[i]; + const char *name = ecs_get_name(world, e); + + /* Parse the signature and add the result to the entity */ + EcsSignature sig = {0}; + ecs_sig_init(world, name, signature[0].expr, &sig.signature); + ecs_set_ptr(world, e, EcsSignature, &sig); + + /* If sig has FromSystem columns, add components to the entity */ + ecs_vector_each(sig.signature.columns, ecs_sig_column_t, column, { + if (column->from_kind == EcsFromSystem) { + ecs_add_entity(world, e, column->is.component); + } + }); + } +} + +/* Create a query from a signature */ +static +void CreateQuery( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + ecs_entity_t *entities = it->entities; + + EcsSignature *signature = ecs_column(it, EcsSignature, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = entities[i]; + + if (!ecs_has(world, e, EcsQuery)) { + EcsQuery query = {0}; + query.query = ecs_query_new_w_sig(world, e, &signature[i].signature); + ecs_set_ptr(world, e, EcsQuery, &query); + } + } +} + +/* Create a system from a query and an action */ +static +void CreateSystem( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + ecs_entity_t *entities = it->entities; + + EcsQuery *query = ecs_column(it, EcsQuery, 1); + EcsIterAction *action = ecs_column(it, EcsIterAction, 2); + EcsContext *ctx = ecs_column(it, EcsContext, 3); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = entities[i]; + void *ctx_ptr = NULL; + if (ctx) { + ctx_ptr = (void*)ctx[i].ctx; + } + + ecs_init_system(world, e, action[i].action, query[i].query, ctx_ptr); + } +} + +static +void bootstrap_set_system( + ecs_world_t *world, + const char *name, + const char *expr, + ecs_iter_action_t action) +{ + ecs_sig_t sig = {0}; + ecs_entity_t sys = ecs_set(world, 0, EcsName, {.value = name}); + ecs_add_entity(world, sys, EcsOnSet); + ecs_sig_init(world, name, expr, &sig); + ecs_query_t *query = ecs_query_new_w_sig(world, sys, &sig); + ecs_init_system(world, sys, action, query, NULL); +} + +ecs_entity_t ecs_new_system( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + ecs_entity_t tag, + const char *signature, + ecs_iter_action_t action) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(!world->in_progress, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t result = ecs_lookup_w_id(world, e, name); + if (!result) { + result = ecs_new_entity(world, 0, name, NULL); + } + + if (tag) { + ecs_add_entity(world, result, tag); + } + + bool added = false; + EcsSignatureExpr *expr = ecs_get_mut(world, result, EcsSignatureExpr, &added); + if (added) { + expr->expr = signature; + } else { + if (!expr->expr || !signature) { + if (expr->expr != signature) { + if (expr->expr && !strcmp(expr->expr, "0")) { + /* Ok */ + } else if (signature && !strcmp(signature, "0")) { + /* Ok */ + } else { + ecs_abort(ECS_ALREADY_DEFINED, NULL); + } + } + } else { + if (strcmp(expr->expr, signature)) { + ecs_abort(ECS_ALREADY_DEFINED, name); + } + } + } + + ecs_modified(world, result, EcsSignatureExpr); + + ecs_set(world, result, EcsIterAction, {action}); + + return result; +} + +ecs_entity_t ecs_new_trigger( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + ecs_entity_t kind, + const char *component_name, + ecs_iter_action_t action) +{ + assert(world->magic == ECS_WORLD_MAGIC); + + ecs_entity_t component = ecs_lookup_fullpath(world, component_name); + ecs_assert(component != 0, ECS_INVALID_COMPONENT_ID, component_name); + + ecs_entity_t result = ecs_lookup_w_id(world, e, name); + if (!result) { + result = ecs_new_entity(world, 0, name, NULL); + } + + bool added = false; + EcsTrigger *trigger = ecs_get_mut(world, result, EcsTrigger, &added); + if (added) { + trigger->kind = kind; + trigger->action = action; + trigger->component = component; + trigger->ctx = NULL; + } else { + if (trigger->kind != kind) { + ecs_abort(ECS_ALREADY_DEFINED, name); + } + + if (trigger->component != component) { + ecs_abort(ECS_ALREADY_DEFINED, name); + } + + if (trigger->action != action) { + trigger->action = action; + } + } + + ecs_modified(world, result, EcsTrigger); + + return result; +} + +void FlecsSystemImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsSystem); + + ecs_set_name_prefix(world, "Ecs"); + + ecs_bootstrap_component(world, EcsComponentLifecycle); + ecs_bootstrap_component(world, EcsTrigger); + ecs_bootstrap_component(world, EcsSystem); + ecs_bootstrap_component(world, EcsTickSource); + ecs_bootstrap_component(world, EcsSignatureExpr); + ecs_bootstrap_component(world, EcsSignature); + ecs_bootstrap_component(world, EcsQuery); + ecs_bootstrap_component(world, EcsIterAction); + ecs_bootstrap_component(world, EcsContext); + + ecs_bootstrap_tag(world, EcsOnAdd); + ecs_bootstrap_tag(world, EcsOnRemove); + ecs_bootstrap_tag(world, EcsOnSet); + ecs_bootstrap_tag(world, EcsUnSet); + + ecs_bootstrap_tag(world, EcsDisabledIntern); + ecs_bootstrap_tag(world, EcsInactive); + + /* Put EcsOnDemand and EcsMonitor in flecs.core so they can be looked up + * without using the flecs.systems prefix */ + ecs_entity_t old_scope = ecs_set_scope(world, EcsFlecsCore); + ecs_bootstrap_tag(world, EcsOnDemand); + ecs_bootstrap_tag(world, EcsMonitor); + ecs_set_scope(world, old_scope); + + ECS_TYPE_IMPL(EcsComponentLifecycle); + ECS_TYPE_IMPL(EcsTrigger); + ECS_TYPE_IMPL(EcsSystem); + ECS_TYPE_IMPL(EcsTickSource); + ECS_TYPE_IMPL(EcsSignatureExpr); + ECS_TYPE_IMPL(EcsSignature); + ECS_TYPE_IMPL(EcsQuery); + ECS_TYPE_IMPL(EcsIterAction); + ECS_TYPE_IMPL(EcsContext); + + /* Bootstrap ctor and dtor for EcsSystem */ + ecs_set_component_actions_w_entity(world, ecs_typeid(EcsSystem), + &(EcsComponentLifecycle) { + .ctor = sys_ctor_init_zero, + .dtor = ecs_colsystem_dtor + }); + + /* Create systems necessary to create systems */ + bootstrap_set_system(world, "CreateSignature", "SignatureExpr", CreateSignature); + bootstrap_set_system(world, "CreateQuery", "Signature, IterAction", CreateQuery); + bootstrap_set_system(world, "CreateSystem", "Query, IterAction, ?Context", CreateSystem); + + /* From here we can create systems */ + + /* Register OnSet system for EcsComponentLifecycle */ + ECS_SYSTEM(world, OnSetComponentLifecycle, EcsOnSet, ComponentLifecycle, SYSTEM:Hidden); + + /* Register OnSet system for triggers */ + ECS_SYSTEM(world, OnSetTrigger, EcsOnSet, Trigger, SYSTEM:Hidden); + + /* System that sets ctx for a trigger */ + ECS_SYSTEM(world, OnSetTriggerCtx, EcsOnSet, Trigger, Context, SYSTEM:Hidden); + + /* Monitors that trigger when a system is enabled or disabled */ + ECS_SYSTEM(world, DisableSystem, EcsMonitor, System, Disabled || DisabledIntern, SYSTEM:Hidden); + ECS_SYSTEM(world, EnableSystem, EcsMonitor, System, !Disabled, !DisabledIntern, SYSTEM:Hidden); +} + +#endif + + +#ifdef FLECS_SYSTEM +#ifdef FLECS_DBG + + +int ecs_dbg_system( + ecs_world_t *world, + ecs_entity_t system, + ecs_dbg_system_t *dbg_out) +{ + const EcsSystem *system_data = ecs_get(world, system, EcsSystem); + if (!system_data) { + return -1; + } + + *dbg_out = (ecs_dbg_system_t){.system = system}; + dbg_out->active_table_count = ecs_vector_count(system_data->query->tables); + dbg_out->inactive_table_count = ecs_vector_count(system_data->query->empty_tables); + dbg_out->enabled = !ecs_has_entity(world, system, EcsDisabled); + + ecs_vector_each(system_data->query->tables, ecs_matched_table_t, mt, { + ecs_table_t *table = mt->iter_data.table; + if (table) { + dbg_out->entities_matched_count += ecs_table_count(table); + } + }); + + /* Inactive tables are inactive because they are empty, so no need to + * iterate them */ + + return 0; +} + +bool ecs_dbg_match_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t system, + ecs_match_failure_t *failure_info_out) +{ + ecs_dbg_entity_t dbg; + ecs_dbg_entity(world, entity, &dbg); + + const EcsSystem *system_data = ecs_get(world, system, EcsSystem); + if (!system_data) { + failure_info_out->reason = EcsMatchNotASystem; + failure_info_out->column = -1; + return false; + } + + return ecs_query_match( + world, dbg.table, system_data->query, failure_info_out); +} + +ecs_type_t ecs_dbg_get_column_type( + ecs_world_t *world, + ecs_entity_t system, + int32_t column_index) +{ + const EcsSystem *system_data = ecs_get(world, system, EcsSystem); + if (!system_data) { + return NULL; + } + + ecs_sig_column_t *columns = ecs_vector_first( + system_data->query->sig.columns, ecs_sig_column_t); + int32_t count = ecs_vector_count(system_data->query->sig.columns); + + if (count < column_index) { + return NULL; + } + + ecs_sig_column_t *column = &columns[column_index - 1]; + ecs_sig_oper_kind_t oper_kind = column->oper_kind; + ecs_type_t result; + + switch(oper_kind) { + case EcsOperOr: + result = column->is.type; + break; + default: + result = ecs_type_from_entity(world, column->is.component); + break; + } + + return result; +} + +#endif +#endif + +/** Parse callback that adds type to type identifier for ecs_new_type */ +static +int parse_type_action( + ecs_world_t *world, + const char *name, + const char *sig, + int64_t column, + ecs_sig_from_kind_t from_kind, + ecs_sig_oper_kind_t oper_kind, + ecs_sig_inout_kind_t inout_kind, + ecs_entity_t role, + const char *entity_id, + const char *source_id, + const char *trait_id, + const char *arg_name, + void *data) +{ + ecs_vector_t **array = data; + (void)source_id; + (void)inout_kind; + + if (arg_name) { + ecs_parser_error(name, sig, column, + "column names not supported in type expression"); + return -1; + } + + if (strcmp(entity_id, "0")) { + ecs_entity_t entity = 0; + + if (from_kind != EcsFromOwned) { + if (!name) { + return -1; + } + + ecs_parser_error(name, sig, column, + "source modifiers not supported for type expressions"); + return -1; + } + + entity = ecs_lookup_fullpath(world, entity_id); + if (!entity) { + if (!name) { + return -1; + } + + ecs_parser_error(name, sig, column, + "unresolved identifier '%s'", entity_id); + return -1; + } + + if (trait_id) { + ecs_entity_t trait = ecs_lookup_fullpath(world, trait_id); + if (!trait) { + ecs_parser_error(name, sig, column, + "unresolved trait identifier '%s'", trait_id); + return -1; + } + + entity = ecs_entity_t_comb(entity, trait); + } + + if (oper_kind == EcsOperAnd) { + ecs_entity_t* e_ptr = ecs_vector_add(array, ecs_entity_t); + *e_ptr = entity | role; + } else { + if (!name) { + return -1; + } + + /* Only AND and OR operators are supported for type expressions */ + ecs_parser_error(name, sig, column, + "invalid operator for type expression"); + return -1; + } + } + + return 0; +} + +static +ecs_table_t* table_from_vec( + ecs_world_t *world, + ecs_vector_t *vec) +{ + ecs_entity_t *array = ecs_vector_first(vec, ecs_entity_t); + int32_t count = ecs_vector_count(vec); + + ecs_entities_t entities = { + .array = array, + .count = count + }; + + return ecs_table_find_or_create(world, &entities); +} + +static +EcsType type_from_vec( + ecs_world_t *world, + ecs_vector_t *vec) +{ + EcsType result = {0, 0}; + ecs_table_t *table = table_from_vec(world, vec); + if (!table) { + return result; + } + + result.type = table->type; + + /* Create normalized type. A normalized type resolves all elements with an + * AND flag and appends them to the resulting type, where the default type + * maintains the original type hierarchy. */ + ecs_vector_t *normalized = NULL; + + ecs_entity_t *array = ecs_vector_first(vec, ecs_entity_t); + int32_t i, count = ecs_vector_count(vec); + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, AND)) { + ecs_entity_t entity = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, entity, EcsType); + ecs_assert(type_ptr != NULL, ECS_INVALID_PARAMETER, + "flag must be applied to type"); + + ecs_vector_each(type_ptr->normalized, ecs_entity_t, c_ptr, { + ecs_entity_t *el = ecs_vector_add(&normalized, ecs_entity_t); + *el = *c_ptr; + }) + } + } + + /* Only get normalized type if it's different from the type */ + if (normalized) { + ecs_entities_t normalized_array = ecs_type_to_entities(normalized); + ecs_table_t *norm_table = ecs_table_traverse_add( + world, table, &normalized_array, NULL); + + result.normalized = norm_table->type; + + ecs_vector_free(normalized); + } else { + result.normalized = result.type; + } + + return result; +} + +static +EcsType type_from_expr( + ecs_world_t *world, + const char *name, + const char *expr) +{ + if (expr) { + ecs_vector_t *vec = ecs_vector_new(ecs_entity_t, 1); + ecs_parse_expr(world, name, expr, parse_type_action, &vec); + EcsType result = type_from_vec(world, vec); + ecs_vector_free(vec); + return result; + } else { + return (EcsType){0, 0}; + } +} + +/* If a name prefix is set with ecs_set_name_prefix, check if the entity name + * has the prefix, and if so remove it. This enables using prefixed names in C + * for components / systems while storing a canonical / language independent + * identifier. */ +const char* ecs_name_from_symbol( + ecs_world_t *world, + const char *type_name) +{ + const char *prefix = world->name_prefix; + if (type_name && prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(type_name, prefix, len) && + (isupper(type_name[len]) || type_name[len] == '_')) + { + if (type_name[len] == '_') { + return type_name + len + 1; + } else { + return type_name + len; + } + } + } + + return type_name; +} + +void ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t e, + const char *name) +{ + if (!name) { + return; + } + + const char *e_name = ecs_name_from_symbol(world, name); + + ecs_set(world, e, EcsName, { + .value = e_name, + .symbol = name + }); +} + +ecs_entity_t ecs_lookup_w_id( + ecs_world_t *world, + ecs_entity_t e, + const char *name) +{ + if (e) { + if (name) { + /* Make sure name is the same */ + const char *existing = ecs_get_name(world, e); + if (existing && strcmp(existing, name)) { + ecs_abort(ECS_INCONSISTENT_NAME, name); + } + if (!existing) { + ecs_set_symbol(world, e, name); + } + } + } + + ecs_entity_t result = e; + if (!result) { + if (!name) { + /* If neither an id nor name is specified, return 0 */ + return 0; + } + + result = ecs_lookup(world, name); + } + + return result; +} + +/* -- Public functions -- */ + +ecs_type_t ecs_type_from_str( + ecs_world_t *world, + const char *expr) +{ + EcsType type = type_from_expr(world, NULL, expr); + return type.normalized; +} + +ecs_table_t* ecs_table_from_str( + ecs_world_t *world, + const char *expr) +{ + if (expr) { + ecs_vector_t *vec = ecs_vector_new(ecs_entity_t, 1); + ecs_parse_expr(world, NULL, expr, parse_type_action, &vec); + ecs_table_t *result = table_from_vec(world, vec); + ecs_vector_free(vec); + return result; + } else { + return NULL; + } +} + +ecs_entity_t ecs_new_entity( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + const char *expr) +{ + ecs_entity_t result = ecs_lookup_w_id(world, e, name); + if (!result) { + result = ecs_new(world, 0); + ecs_set_symbol(world, result, name); + } + + EcsType type = type_from_expr(world, name, expr); + ecs_add_type(world, result, type.normalized); + + return result; +} + +ecs_entity_t ecs_new_prefab( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + const char *expr) +{ + ecs_entity_t result = ecs_lookup_w_id(world, e, name); + if (!result) { + result = ecs_new(world, 0); + ecs_set_symbol(world, result, name); + } + + ecs_add_entity(world, result, EcsPrefab); + + EcsType type = type_from_expr(world, name, expr); + ecs_add_type(world, result, type.normalized); + + return result; +} + +ecs_entity_t ecs_new_component( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + size_t size, + size_t alignment) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + assert(world->magic == ECS_WORLD_MAGIC); + bool in_progress = world->in_progress; + + /* If world is in progress component may be registered, but only when not + * in multithreading mode. */ + if (in_progress) { + ecs_assert(ecs_vector_count(world->workers) < 1, + ECS_INVALID_WHILE_ITERATING, NULL); + + /* Component creation should not be deferred */ + ecs_defer_end(world); + world->in_progress = false; + } + + ecs_entity_t result = ecs_lookup_w_id(world, e, name); + if (!result) { + result = ecs_new_component_id(world); + ecs_set_symbol(world, result, name); + } + + /* ecs_new_component_id does not add the scope, so add it explicitly */ + ecs_entity_t scope = world->stage.scope; + if (scope) { + ecs_add_entity(world, result, ECS_CHILDOF | scope); + } + + bool added = false; + EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent, &added); + + if (added) { + ptr->size = ecs_from_size_t(size); + ptr->alignment = ecs_from_size_t(alignment); + } else { + if (ptr->size != ecs_from_size_t(size)) { + ecs_abort(ECS_INVALID_COMPONENT_SIZE, name); + } + if (ptr->alignment != ecs_from_size_t(alignment)) { + ecs_abort(ECS_INVALID_COMPONENT_SIZE, name); + } + } + + ecs_modified(world, result, EcsComponent); + + if (e > world->stats.last_component_id && e < ECS_HI_COMPONENT_ID) { + world->stats.last_component_id = e + 1; + } + + if (in_progress) { + world->in_progress = true; + ecs_defer_begin(world); + } + + return result; +} + +ecs_entity_t ecs_new_type( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + const char *expr) +{ + assert(world->magic == ECS_WORLD_MAGIC); + + ecs_entity_t result = ecs_lookup_w_id(world, e, name); + if (!result) { + result = ecs_new_entity(world, 0, name, NULL); + } + + EcsType type_parsed = type_from_expr(world, name, expr); + + bool added = false; + EcsType *type = ecs_get_mut(world, result, EcsType, &added); + if (added) { + type->type = type_parsed.type; + type->normalized = type_parsed.normalized; + } else { + if (type->type != type_parsed.type) { + ecs_abort(ECS_ALREADY_DEFINED, name); + } + + if (type->normalized != type_parsed.normalized) { + ecs_abort(ECS_ALREADY_DEFINED, name); + } + } + + /* This will allow the type to show up in debug tools */ + ecs_map_set(world->type_handles, (uintptr_t)type_parsed.type, &result); + + return result; +} + +/* Global type variables */ +ecs_type_t ecs_type(EcsComponent); +ecs_type_t ecs_type(EcsType); +ecs_type_t ecs_type(EcsName); +ecs_type_t ecs_type(EcsPrefab); + +/* Component lifecycle actions for EcsName */ +static ECS_CTOR(EcsName, ptr, { + ptr->value = NULL; + ptr->alloc_value = NULL; + ptr->symbol = NULL; +}) + +static ECS_DTOR(EcsName, ptr, { + ecs_os_free(ptr->alloc_value); + ptr->value = NULL; + ptr->alloc_value = NULL; + ptr->symbol = NULL; +}) + +static ECS_COPY(EcsName, dst, src, { + if (dst->alloc_value) { + ecs_os_free(dst->alloc_value); + dst->alloc_value = NULL; + } + + if (src->alloc_value) { + dst->alloc_value = ecs_os_strdup(src->alloc_value); + dst->value = dst->alloc_value; + } else { + dst->alloc_value = NULL; + dst->value = src->value; + } + dst->symbol = src->symbol; +}) + +static ECS_MOVE(EcsName, dst, src, { + dst->value = src->value; + dst->alloc_value = src->alloc_value; + dst->symbol = src->symbol; + + src->value = NULL; + src->alloc_value = NULL; + src->symbol = NULL; +}) + +/* -- Bootstrapping -- */ + +#define bootstrap_component(world, table, name)\ + _bootstrap_component(world, table, ecs_typeid(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) + +static +void _bootstrap_component( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + const char *id, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = ecs_table_get_or_create_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_column_t *columns = data->columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Create record in entity index */ + ecs_record_t *record = ecs_eis_get_or_create(world, entity); + record->table = table; + + /* Insert row into table to store EcsComponent itself */ + int32_t index = ecs_table_append(world, table, data, entity, record, false); + record->row = index + 1; + + /* Set size and id */ + EcsComponent *c_info = ecs_vector_first(columns[0].data, EcsComponent); + EcsName *id_data = ecs_vector_first(columns[1].data, EcsName); + + c_info[index].size = size; + c_info[index].alignment = alignment; + id_data[index].value = &id[ecs_os_strlen("Ecs")]; /* Skip prefix */ + id_data[index].symbol = id; + id_data[index].alloc_value = NULL; +} + +/** Create type for component */ +ecs_type_t ecs_bootstrap_type( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_table_t *table = ecs_table_find_or_create(world, &(ecs_entities_t){ + .array = (ecs_entity_t[]){entity}, + .count = 1 + }); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->type != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +/** Bootstrap types for builtin components and tags */ +static +void bootstrap_types( + ecs_world_t *world) +{ + ecs_type(EcsComponent) = ecs_bootstrap_type(world, ecs_typeid(EcsComponent)); + ecs_type(EcsType) = ecs_bootstrap_type(world, ecs_typeid(EcsType)); + ecs_type(EcsName) = ecs_bootstrap_type(world, ecs_typeid(EcsName)); +} + +/** Initialize component table. This table is manually constructed to bootstrap + * flecs. After this function has been called, the builtin components can be + * created. + * The reason this table is constructed manually is because it requires the size + * and alignment of the EcsComponent and EcsName components, which haven't been + * created yet */ +static +ecs_table_t* bootstrap_component_table( + ecs_world_t *world) +{ + ecs_entity_t entities[] = {ecs_typeid(EcsComponent), ecs_typeid(EcsName), ECS_CHILDOF | EcsFlecsCore}; + ecs_entities_t array = { + .array = entities, + .count = 3 + }; + + ecs_table_t *result = ecs_table_find_or_create(world, &array); + ecs_data_t *data = ecs_table_get_or_create_data(result); + + /* Preallocate enough memory for initial components */ + data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId); + data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId); + + data->columns = ecs_os_malloc(sizeof(ecs_column_t) * 2); + ecs_assert(data->columns != NULL, ECS_OUT_OF_MEMORY, NULL); + + data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId); + data->columns[0].size = sizeof(EcsComponent); + data->columns[0].alignment = ECS_ALIGNOF(EcsComponent); + data->columns[1].data = ecs_vector_new(EcsName, EcsFirstUserComponentId); + data->columns[1].size = sizeof(EcsName); + data->columns[1].alignment = ECS_ALIGNOF(EcsName); + + result->column_count = 2; + + return result; +} + +void ecs_bootstrap( + ecs_world_t *world) +{ + ecs_type(EcsComponent) = NULL; + + ecs_trace_1("bootstrap core components"); + ecs_log_push(); + + /* Create table that will hold components (EcsComponent, EcsName) */ + ecs_table_t *table = bootstrap_component_table(world); + assert(table != NULL); + + bootstrap_component(world, table, EcsComponent); + bootstrap_component(world, table, EcsType); + bootstrap_component(world, table, EcsName); + + world->stats.last_component_id = EcsFirstUserComponentId; + world->stats.last_id = EcsFirstUserEntityId; + world->stats.min_id = 0; + world->stats.max_id = 0; + + bootstrap_types(world); + + ecs_set_scope(world, EcsFlecsCore); + + ecs_bootstrap_tag(world, EcsModule); + ecs_bootstrap_tag(world, EcsPrefab); + ecs_bootstrap_tag(world, EcsHidden); + ecs_bootstrap_tag(world, EcsDisabled); + + ecs_set_component_actions(world, EcsName, { + .ctor = ecs_ctor(EcsName), + .dtor = ecs_dtor(EcsName), + .copy = ecs_copy(EcsName), + .move = ecs_move(EcsName) + }); + + /* Initialize scopes */ + ecs_set(world, EcsFlecs, EcsName, {.value = "flecs"}); + ecs_add_entity(world, EcsFlecs, EcsModule); + ecs_set(world, EcsFlecsCore, EcsName, {.value = "core"}); + ecs_add_entity(world, EcsFlecsCore, EcsModule); + ecs_add_entity(world, EcsFlecsCore, ECS_CHILDOF | EcsFlecs); + + /* Initialize EcsWorld */ + ecs_set(world, EcsWorld, EcsName, {.value = "World"}); + ecs_assert(ecs_get_name(world, EcsWorld) != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_lookup(world, "World") == EcsWorld, ECS_INTERNAL_ERROR, NULL); + ecs_add_entity(world, EcsWorld, ECS_CHILDOF | EcsFlecsCore); + + /* Initialize EcsSingleton */ + ecs_set(world, EcsSingleton, EcsName, {.value = "$"}); + ecs_assert(ecs_get_name(world, EcsSingleton) != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_lookup(world, "$") == EcsSingleton, ECS_INTERNAL_ERROR, NULL); + ecs_add_entity(world, EcsSingleton, ECS_CHILDOF | EcsFlecsCore); + + /* Initialize EcsWildcard */ + ecs_set(world, EcsWildcard, EcsName, {.value = "*"}); + ecs_assert(ecs_get_name(world, EcsWildcard) != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_lookup(world, "*") == EcsWildcard, ECS_INTERNAL_ERROR, NULL); + ecs_add_entity(world, EcsWildcard, ECS_CHILDOF | EcsFlecsCore); + + ecs_set_scope(world, 0); + + ecs_log_pop(); +} + + +static +bool path_append( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + ecs_entity_t component, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf) +{ + ecs_type_t type = ecs_get_type(world, child); + ecs_entity_t cur = ecs_find_in_type(world, type, component, ECS_CHILDOF); + + if (cur) { + if (cur != parent && cur != EcsFlecsCore) { + path_append(world, parent, cur, component, sep, prefix, buf); + ecs_strbuf_appendstr(buf, sep); + } + } else if (prefix) { + ecs_strbuf_appendstr(buf, prefix); + } + + char buff[22]; + const char *name = ecs_get_name(world, child); + if (!name) { + ecs_os_sprintf(buff, "%u", (uint32_t)child); + name = buff; + } + + ecs_strbuf_appendstr(buf, name); + + return cur != 0; +} + +static +ecs_entity_t find_as_alias( + ecs_world_t *world, + const char *name) +{ + int32_t i, count = ecs_vector_count(world->aliases); + ecs_alias_t *aliases = ecs_vector_first(world->aliases, ecs_alias_t); + for (i = 0; i < count; i ++) { + if (!strcmp(aliases[i].name, name)) { + return aliases[i].entity; + } + } + + return 0; +} + +char* ecs_get_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + ecs_entity_t component, + const char *sep, + const char *prefix) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (parent != child) { + path_append(world, parent, child, component, sep, prefix, &buf); + } else { + ecs_strbuf_appendstr(&buf, ""); + } + + return ecs_strbuf_get(&buf); +} + +static +bool is_number( + const char *name) +{ + if (!isdigit(name[0])) { + return false; + } + + ecs_size_t i, s = ecs_os_strlen(name); + for (i = 1; i < s; i ++) { + if (!isdigit(name[i])) { + break; + } + } + + return i >= s; +} + +static +ecs_entity_t name_to_id( + const char *name) +{ + long int result = atol(name); + ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); + return (ecs_entity_t)result; +} + +static +ecs_entity_t find_child_in_table( + ecs_table_t *table, + const char *name) +{ + /* If table doesn't have EcsName, then don't bother */ + int32_t name_index = ecs_type_index_of(table->type, ecs_typeid(EcsName)); + if (name_index == -1) { + return 0; + } + + ecs_data_t *data = ecs_table_get_data(table); + if (!data || !data->columns) { + return 0; + } + + int32_t i, count = ecs_vector_count(data->entities); + if (!count) { + return 0; + } + + if (is_number(name)) { + return name_to_id(name); + } + + ecs_column_t *column = &data->columns[name_index]; + EcsName *names = ecs_vector_first(column->data, EcsName); + + for (i = 0; i < count; i ++) { + const char *cur_name = names[i].value; + const char *cur_sym = names[i].symbol; + if ((cur_name && !strcmp(cur_name, name)) || (cur_sym && !strcmp(cur_sym, name))) { + return *ecs_vector_get(data->entities, ecs_entity_t, i); + } + } + + return 0; +} + +static +ecs_entity_t find_child( + ecs_world_t *world, + ecs_entity_t parent, + const char *name) +{ + (void)parent; + + ecs_sparse_each(world->store.tables, ecs_table_t, table, { + ecs_entity_t result = find_child_in_table(table, name); + if (result) { + return result; + } + }); + + return 0; +} + +ecs_entity_t ecs_lookup_child( + ecs_world_t *world, + ecs_entity_t parent, + const char *name) +{ + ecs_entity_t result = 0; + + ecs_vector_t *child_tables = ecs_map_get_ptr( + world->child_tables, ecs_vector_t*, parent); + + if (child_tables) { + ecs_vector_each(child_tables, ecs_table_t*, table_ptr, { + ecs_table_t *table = *table_ptr; + result = find_child_in_table(table, name); + if (result) { + return result; + } + }); + } + + return result; +} + +ecs_entity_t ecs_lookup( + ecs_world_t *world, + const char *name) +{ + if (!name) { + return 0; + } + + if (is_number(name)) { + return name_to_id(name); + } + + ecs_entity_t e = find_as_alias(world, name); + if (e) { + return e; + } + + return ecs_lookup_child(world, 0, name); +} + +ecs_entity_t ecs_lookup_symbol( + ecs_world_t *world, + const char *name) +{ + if (!name) { + return 0; + } + + if (is_number(name)) { + return name_to_id(name); + } + + return find_child(world, 0, name); +} + +static +bool is_sep( + const char **ptr, + const char *sep) +{ + ecs_size_t len = ecs_os_strlen(sep); + + if (!ecs_os_strncmp(*ptr, sep, len)) { + *ptr += len - 1; + return true; + } else { + return false; + } +} + +static +const char *path_elem( + const char *path, + char *buff, + const char *sep) +{ + const char *ptr; + char *bptr, ch; + + for (bptr = buff, ptr = path; (ch = *ptr); ptr ++) { + ecs_assert(bptr - buff < ECS_MAX_NAME_LENGTH, ECS_INVALID_PARAMETER, + NULL); + + if (is_sep(&ptr, sep)) { + *bptr = '\0'; + return ptr + 1; + } else { + *bptr = ch; + bptr ++; + } + } + + if (bptr != buff) { + *bptr = '\0'; + return ptr; + } else { + return NULL; + } +} + +static +ecs_entity_t get_parent_from_path( + ecs_world_t *world, + ecs_entity_t parent, + const char **path_ptr, + const char *prefix) +{ + bool start_from_root = false; + const char *path = *path_ptr; + + if (prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(path, prefix, len)) { + path += len; + parent = 0; + start_from_root = true; + } + } + + if (!start_from_root && !parent) { + ecs_stage_t *stage = ecs_get_stage(&world); + parent = stage->scope; + } + + *path_ptr = path; + + return parent; +} + +ecs_entity_t ecs_lookup_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) +{ + if (!path) { + return 0; + } + + ecs_entity_t e = find_as_alias(world, path); + if (e) { + return e; + } + + char buff[ECS_MAX_NAME_LENGTH]; + const char *ptr; + ecs_entity_t cur; + bool core_searched = false; + + if (!sep) { + sep = "."; + } + + parent = get_parent_from_path(world, parent, &path, prefix); + +retry: + cur = parent; + ptr = path; + + while ((ptr = path_elem(ptr, buff, sep))) { + cur = ecs_lookup_child(world, cur, buff); + if (!cur) { + goto tail; + } + } + +tail: + if (!cur) { + if (!core_searched) { + if (parent) { + parent = ecs_get_parent_w_entity(world, parent, 0); + } else { + parent = EcsFlecsCore; + core_searched = true; + } + goto retry; + } + } + + return cur; +} + +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + + ecs_entity_t e = ECS_CHILDOF | scope; + ecs_entities_t to_add = { + .array = &e, + .count = 1 + }; + + ecs_entity_t cur = stage->scope; + stage->scope = scope; + + if (scope) { + stage->scope_table = ecs_table_traverse_add( + world, &world->store.root, &to_add, NULL); + } else { + stage->scope_table = &world->store.root; + } + + return cur; +} + +ecs_entity_t ecs_get_scope( + ecs_world_t *world) +{ + ecs_stage_t *stage = ecs_get_stage(&world); + return stage->scope; +} + +int32_t ecs_get_child_count( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_vector_t *tables = ecs_map_get_ptr(world->child_tables, ecs_vector_t*, entity); + if (!tables) { + return 0; + } else { + int32_t count = 0; + + ecs_vector_each(tables, ecs_table_t*, table_ptr, { + ecs_table_t *table = *table_ptr; + count += ecs_table_count(table); + }); + + return count; + } +} + +ecs_iter_t ecs_scope_iter( + ecs_world_t *world, + ecs_entity_t parent) +{ + ecs_scope_iter_t iter = { + .tables = ecs_map_get_ptr(world->child_tables, ecs_vector_t*, parent), + .index = 0 + }; + + return (ecs_iter_t) { + .world = world, + .iter.parent = iter + }; +} + +ecs_iter_t ecs_scope_iter_w_filter( + ecs_world_t *world, + ecs_entity_t parent, + ecs_filter_t *filter) +{ + ecs_scope_iter_t iter = { + .filter = *filter, + .tables = ecs_map_get_ptr(world->child_tables, ecs_vector_t*, parent), + .index = 0 + }; + + return (ecs_iter_t) { + .world = world, + .iter.parent = iter, + .table_count = ecs_vector_count(iter.tables) + }; +} + +bool ecs_scope_next( + ecs_iter_t *it) +{ + ecs_scope_iter_t *iter = &it->iter.parent; + ecs_vector_t *tables = iter->tables; + ecs_filter_t filter = iter->filter; + + int32_t count = ecs_vector_count(tables); + int32_t i; + + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = *ecs_vector_get(tables, ecs_table_t*, i); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = ecs_table_get_data(table); + if (!data) { + continue; + } + + it->count = ecs_table_count(table); + if (!it->count) { + continue; + } + + if (filter.include || filter.exclude) { + if (!ecs_table_match_filter(it->world, table, &filter)) { + continue; + } + } + + iter->table.table = table; + it->table = &iter->table; + it->table_columns = data->columns; + it->count = ecs_table_count(table); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + iter->index = i + 1; + + return true; + } + + return false; +} + +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) +{ + const char *old_prefix = world->name_prefix; + world->name_prefix = prefix; + return old_prefix; +} + +ecs_entity_t ecs_add_path_w_sep( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) +{ + if (!path) { + if (!entity) { + entity = ecs_new_id(world); + } + + if (parent) { + ecs_add_entity(world, entity, ECS_CHILDOF | entity); + } + + return entity; + } + + char buff[ECS_MAX_NAME_LENGTH]; + const char *ptr = path; + + parent = get_parent_from_path(world, parent, &path, prefix); + + ecs_entity_t cur = parent; + + while ((ptr = path_elem(ptr, buff, sep))) { + ecs_entity_t e = ecs_lookup_child(world, cur, buff); + if (!e) { + char *name = ecs_os_strdup(buff); + + /* If this is the last entity in the path, use the provided id */ + if (entity && !path_elem(ptr, buff, sep)) { + e = entity; + } + + e = ecs_set(world, e, EcsName, { + .value = name, + .alloc_value = name + }); + + ecs_os_free(name); + + if (cur) { + ecs_add_entity(world, e, ECS_CHILDOF | cur); + } + } + + cur = e; + } + + return cur; +} + +ecs_entity_t ecs_new_from_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) +{ + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); +} + +void ecs_use( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = ecs_get_stage(&world); + ecs_assert(stage->scope == 0 , ECS_INVALID_PARAMETER, NULL); + ecs_assert(find_as_alias(world, name) == 0, ECS_ALREADY_DEFINED, NULL); + (void)stage; + + ecs_alias_t *al = ecs_vector_add(&world->aliases, ecs_alias_t); + al->name = ecs_os_strdup(name); + al->entity = entity; +} diff --git a/code/vendors/flecs/flecs.h b/code/vendors/flecs/flecs.h new file mode 100644 index 0000000..985d90a --- /dev/null +++ b/code/vendors/flecs/flecs.h @@ -0,0 +1,12994 @@ +#define flecs_STATIC + +#ifndef FLECS_H +#define FLECS_H + +/* FLECS_LEGACY should be defined when building for C89 */ +// #define FLECS_LEGACY + +/* FLECS_NO_CPP should be defined when building for C++ without the C++ API */ +// #define FLECS_NO_CPP + +/* FLECS_CUSTOM_BUILD should be defined when manually selecting features */ +// #define FLECS_CUSTOM_BUILD + +#ifndef FLECS_CUSTOM_BUILD +/* Modules */ +#define FLECS_SYSTEM +#define FLECS_PIPELINE +#define FLECS_TIMER + +/* Addons */ +#define FLECS_BULK +#define FLECS_DBG +#define FLECS_MODULE +#define FLECS_QUEUE +#define FLECS_READER_WRITER +#define FLECS_SNAPSHOT +#define FLECS_DIRECT_ACCESS +#define FLECS_STATS +#endif + +/* Set to double or int to increase accuracy of time keeping. Note that when + * using an integer type, an application has to provide the delta_time values + * to the progress() function, as the code that measures time requires a + * floating point type. */ +#ifndef FLECS_FLOAT +#define FLECS_FLOAT float +#endif + +/** + * @file api_defines.h + * @brief Supporting defines for the public API. + * + * This file contains constants / macro's that are typically not used by an + * application but support the public API, and therefore must be exposed. This + * header should not be included by itself. + */ + +#ifndef FLECS_API_DEFINES_H +#define FLECS_API_DEFINES_H + +/* Standard library dependencies */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Non-standard but required. If not provided by platform, add manually. */ +#include + +/* Contains macro's for importing / exporting symbols */ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef FLECS_BAKE_CONFIG_H +#define FLECS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +/* No dependencies */ + +/* Convenience macro for exporting symbols */ +#ifndef flecs_STATIC +#if flecs_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__)) + #define FLECS_API __declspec(dllexport) +#elif flecs_EXPORTS + #define FLECS_API __attribute__((__visibility__("default"))) +#elif defined _MSC_VER + #define FLECS_API __declspec(dllimport) +#else + #define FLECS_API +#endif +#else + #define FLECS_API +#endif + +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __BAKE_LEGACY__ +#define FLECS_LEGACY +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Language support defines +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY +#include +#endif + +/* The API uses the native bool type in C++, or a custom one in C */ +#if !defined(__cplusplus) && !defined(__bool_true_false_are_defined) +#undef bool +#undef true +#undef false +typedef char bool; +#define false 0 +#define true !false +#endif + +typedef uint32_t ecs_flags32_t; +typedef uint64_t ecs_flags64_t; + +/* Keep unsigned integers out of the codebase as they do more harm than good */ +typedef int32_t ecs_size_t; +#define ECS_SIZEOF(T) (ecs_size_t)sizeof(T) + +/* Use alignof in C++, or a trick in C. */ +#ifdef __cplusplus +#define ECS_ALIGNOF(T) (int64_t)alignof(T) +#elif defined(_MSC_VER) +#define ECS_ALIGNOF(T) (int64_t)__alignof(T) +#elif defined(__GNUC__) +#define ECS_ALIGNOF(T) (int64_t)__alignof__(T) +#else +#define ECS_ALIGNOF(T) ((int64_t)&((struct { char c; T d; } *)0)->d) +#endif + +#if defined(__GNUC__) +#define ECS_UNUSED __attribute__((unused)) +#else +#define ECS_UNUSED +#endif + +#define ECS_ALIGN(size, alignment) (ecs_size_t)((((((size_t)size) - 1) / ((size_t)alignment)) + 1) * ((size_t)alignment)) + +/* Simple utility for determining the max of two values */ +#define ECS_MAX(a, b) ((a > b) ? a : b) + + +//////////////////////////////////////////////////////////////////////////////// +//// Reserved component ids +//////////////////////////////////////////////////////////////////////////////// + +/** Builtin component ids */ +#define FLECS__EEcsComponent (1) +#define FLECS__EEcsComponentLifecycle (2) +#define FLECS__EEcsType (3) +#define FLECS__EEcsName (6) + +/** System module component ids */ +#define FLECS__EEcsTrigger (4) +#define FLECS__EEcsSystem (5) +#define FLECS__EEcsTickSource (7) +#define FLECS__EEcsSignatureExpr (8) +#define FLECS__EEcsSignature (9) +#define FLECS__EEcsQuery (10) +#define FLECS__EEcsIterAction (11) +#define FLECS__EEcsContext (12) + +/** Pipeline module component ids */ +#define FLECS__EEcsPipelineQuery (13) + +/** Timer module component ids */ +#define FLECS__EEcsTimer (14) +#define FLECS__EEcsRateFilter (15) + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity id macro's +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_ROLE_MASK ((ecs_entity_t)0xFF << 56) +#define ECS_ENTITY_MASK ((uint64_t)0xFFFFFFFF) +#define ECS_GENERATION_MASK ((uint64_t)0xFFFF << 32) +#define ECS_GENERATION(e) ((e & ECS_GENERATION_MASK) >> 32) +#define ECS_GENERATION_INC(e) ((e & ~ECS_GENERATION_MASK) | ((ECS_GENERATION(e) + 1) << 32)) +#define ECS_COMPONENT_MASK ((ecs_entity_t)~ECS_ROLE_MASK) +#define ECS_TYPE_ROLE_START ECS_CHILDOF +#define ECS_HAS_ROLE(e, role) ((e & ECS_ROLE_MASK) == ECS_##role) + + +//////////////////////////////////////////////////////////////////////////////// +//// Convert between C typenames and variables +//////////////////////////////////////////////////////////////////////////////// + +/** Translate C type to ecs_type_t variable. */ +#define ecs_type(T) FLECS__T##T + +/** Translate C type to entity id. */ +#define ecs_typeid(T) FLECS__E##T + +/* DEPRECATED: old way to get entity id from type */ +#define ecs_entity(T) ecs_typeid(T) + +/** Translate C type to module struct. */ +#define ecs_module(T) FLECS__M##T + +/** Translate C type to module struct. */ +#define ecs_module_ptr(T) FLECS__M##T##_ptr + +/** Translate C type to module struct. */ +#define ecs_iter_action(T) FLECS__F##T + +#ifndef FLECS_LEGACY + + +//////////////////////////////////////////////////////////////////////////////// +//// Utilities for working with trait identifiers +//////////////////////////////////////////////////////////////////////////////// + +#define ecs_entity_t_lo(value) ((uint32_t)(value)) +#define ecs_entity_t_hi(value) ((uint32_t)((value) >> 32)) +#define ecs_entity_t_comb(v1, v2) (((uint64_t)(v2) << 32) + (uint32_t)(v1)) +#define ecs_trait(comp, trait) ECS_TRAIT | ecs_entity_t_comb(comp, trait) + + +//////////////////////////////////////////////////////////////////////////////// +//// Convenience macro's for ctor, dtor, move and copy +//////////////////////////////////////////////////////////////////////////////// + +/* Constructor / destructor convenience macro */ +#define ECS_XTOR_IMPL(type, postfix, var, ...)\ + void type##_##postfix(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *entity_ptr,\ + void *_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)entity_ptr;\ + (void)_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t entity = entity_ptr[i];\ + type *var = &((type*)_ptr)[i];\ + (void)entity;\ + (void)var;\ + __VA_ARGS__\ + }\ + } + +/* Copy convenience macro */ +#define ECS_COPY_IMPL(type, dst_var, src_var, ...)\ + void type##_##copy(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *dst_entities,\ + const ecs_entity_t *src_entities,\ + void *_dst_ptr,\ + const void *_src_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)dst_entities;\ + (void)src_entities;\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t dst_entity = dst_entities[i];\ + ecs_entity_t src_entity = src_entities[i];\ + type *dst_var = &((type*)_dst_ptr)[i];\ + type *src_var = &((type*)_src_ptr)[i];\ + (void)dst_entity;\ + (void)src_entity;\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } + +/* Move convenience macro */ +#define ECS_MOVE_IMPL(type, dst_var, src_var, ...)\ + void type##_##move(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *dst_entities,\ + const ecs_entity_t *src_entities,\ + void *_dst_ptr,\ + void *_src_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)dst_entities;\ + (void)src_entities;\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t dst_entity = dst_entities[i];\ + ecs_entity_t src_entity = src_entities[i];\ + type *dst_var = &((type*)_dst_ptr)[i];\ + type *src_var = &((type*)_src_ptr)[i];\ + (void)dst_entity;\ + (void)src_entity;\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } +#endif +#ifdef __cplusplus +} +#endif + +#endif +/* This is an implementation of a simple vector type. The vector is allocated in + * a single block of memory, with the element count, and allocated number of + * elements encoded in the block. As this vector is used for user-types it has + * been designed to support alignments higher than 8 bytes. This makes the size + * of the vector header variable in size. To reduce the overhead associated with + * retrieving or computing this size, the functions are wrapped in macro calls + * that compute the header size at compile time. + * + * The API provides a number of _t macro's, which accept a size and alignment. + * These macro's are used when no compile-time type is available. + * + * The vector guarantees contiguous access to its elements. When an element is + * removed from the vector, the last element is copied to the removed element. + * + * The API requires passing in the type of the vector. This type is used to test + * whether the size of the provided type equals the size of the type with which + * the vector was created. In release mode this check is not performed. + * + * When elements are added to the vector, it will automatically resize to the + * next power of two. This can change the pointer of the vector, which is why + * operations that can increase the vector size, accept a double pointer to the + * vector. + */ + +#ifndef FLECS_VECTOR_H +#define FLECS_VECTOR_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public, so we can do compile-time header size calculation */ +struct ecs_vector_t { + int32_t count; + int32_t size; + +#ifndef NDEBUG + int64_t elem_size; +#endif +}; + +/* Compute the header size of the vector from size & alignment */ +#define ECS_VECTOR_U(size, alignment) size, ECS_MAX(ECS_SIZEOF(ecs_vector_t), alignment) + +/* Compute the header size of the vector from a provided compile-time type */ +#define ECS_VECTOR_T(T) ECS_VECTOR_U(ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +/* Utility macro's for creating vector on stack */ +#ifndef NDEBUG +#define ECS_VECTOR_VALUE(T, elem_count)\ +{\ + .elem_size = (int32_t)(ECS_SIZEOF(T)),\ + .count = elem_count,\ + .size = elem_count\ +} +#else +#define ECS_VECTOR_VALUE(T, elem_count)\ +{\ + .count = elem_count,\ + .size = elem_count\ +} +#endif + +#define ECS_VECTOR_DECL(name, T, elem_count)\ +struct {\ + union {\ + ecs_vector_t vector;\ + uint64_t align;\ + } header;\ + T array[elem_count];\ +} __##name##_value = {\ + .header.vector = ECS_VECTOR_VALUE(T, elem_count)\ +};\ +const ecs_vector_t *name = (ecs_vector_t*)&__##name##_value + +#define ECS_VECTOR_IMPL(name, T, elems, elem_count)\ +ecs_os_memcpy(__##name##_value.array, elems, sizeof(T) * elem_count) + +#define ECS_VECTOR_STACK(name, T, elems, elem_count)\ +ECS_VECTOR_DECL(name, T, elem_count);\ +ECS_VECTOR_IMPL(name, T, elems, elem_count) + +typedef struct ecs_vector_t ecs_vector_t; + +typedef int (*ecs_comparator_t)( + const void* p1, + const void *p2); + +/** Create new vector. */ +FLECS_API +ecs_vector_t* _ecs_vector_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_new(T, elem_count) \ + _ecs_vector_new(ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_new_t(size, alignment, elem_count) \ + _ecs_vector_new(ECS_VECTOR_U(size, alignment), elem_count) + +/* Create new vector, initialize it with provided array */ +FLECS_API +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array); + +#define ecs_vector_from_array(T, elem_count, array)\ + _ecs_vector_from_array(ECS_VECTOR_T(T), elem_count, array) + +/* Initialize vector with zero's */ +FLECS_API +void _ecs_vector_zero( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_zero(vector, T) \ + _ecs_vector_zero(vector, ECS_VECTOR_T(T)) + +/** Free vector */ +FLECS_API +void ecs_vector_free( + ecs_vector_t *vector); + +/** Clear values in vector */ +FLECS_API +void ecs_vector_clear( + ecs_vector_t *vector); + +/** Assert when the provided size does not match the vector type. */ +FLECS_API +void ecs_vector_assert_size( + ecs_vector_t* vector_inout, + ecs_size_t elem_size); + +/** Assert when the provided alignment does not match the vector type. */ +FLECS_API +void ecs_vector_assert_alignment( + ecs_vector_t* vector, + ecs_size_t elem_alignment); + +/** Add element to vector. */ +FLECS_API +void* _ecs_vector_add( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_add(vector, T) \ + ((T*)_ecs_vector_add(vector, ECS_VECTOR_T(T))) + +#define ecs_vector_add_t(vector, size, alignment) \ + _ecs_vector_add(vector, ECS_VECTOR_U(size, alignment)) + +/** Add n elements to the vector. */ +FLECS_API +void* _ecs_vector_addn( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_addn(vector, T, elem_count) \ + ((T*)_ecs_vector_addn(vector, ECS_VECTOR_T(T), elem_count)) + +#define ecs_vector_addn_t(vector, size, alignment, elem_count) \ + _ecs_vector_addn(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Get element from vector. */ +FLECS_API +void* _ecs_vector_get( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_get(vector, T, index) \ + ((T*)_ecs_vector_get(vector, ECS_VECTOR_T(T), index)) + +#define ecs_vector_get_t(vector, size, alignment, index) \ + _ecs_vector_get(vector, ECS_VECTOR_U(size, alignment), index) + +/** Get last element from vector. */ +FLECS_API +void* _ecs_vector_last( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_last(vector, T) \ + (T*)_ecs_vector_last(vector, ECS_VECTOR_T(T)) + +/** Set minimum size for vector. If the current size of the vector is larger, + * the function will have no side effects. */ +FLECS_API +int32_t _ecs_vector_set_min_size( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_min_size(vector, T, size) \ + _ecs_vector_set_min_size(vector, ECS_VECTOR_T(T), size) + +/** Set minimum count for vector. If the current count of the vector is larger, + * the function will have no side effects. */ +FLECS_API +int32_t _ecs_vector_set_min_count( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_min_count(vector, T, size) \ + _ecs_vector_set_min_count(vector, ECS_VECTOR_T(T), size) + +/** Remove last element. This operation requires no swapping of values. */ +FLECS_API +void ecs_vector_remove_last( + ecs_vector_t *vector); + +/** Remove last value, store last element in provided value. */ +FLECS_API +bool _ecs_vector_pop( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + void *value); + +#define ecs_vector_pop(vector, T, value) \ + _ecs_vector_pop(vector, ECS_VECTOR_T(T), value) + +/** Append element at specified index to another vector. */ +FLECS_API +int32_t _ecs_vector_move_index( + ecs_vector_t **dst, + ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_move_index(dst, src, T, index) \ + _ecs_vector_move_index(dst, src, ECS_VECTOR_T(T), index) + +/** Remove element at specified index. Moves the last value to the index. */ +FLECS_API +int32_t _ecs_vector_remove_index( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_remove_index(vector, T, index) \ + _ecs_vector_remove_index(vector, ECS_VECTOR_T(T), index) + +#define ecs_vector_remove_index_t(vector, size, alignment, index) \ + _ecs_vector_remove_index(vector, ECS_VECTOR_U(size, alignment), index) + +/** Shrink vector to make the size match the count. */ +FLECS_API +void _ecs_vector_reclaim( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_reclaim(vector, T)\ + _ecs_vector_reclaim(vector, ECS_VECTOR_T(T)) + +/** Grow size of vector with provided number of elements. */ +FLECS_API +int32_t _ecs_vector_grow( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_grow(vector, T, size) \ + _ecs_vector_grow(vector, ECS_VECTOR_T(T), size) + +/** Set allocation size of vector. */ +FLECS_API +int32_t _ecs_vector_set_size( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_size(vector, T, elem_count) \ + _ecs_vector_set_size(vector, ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_set_size_t(vector, size, alignment, elem_count) \ + _ecs_vector_set_size(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Set count of vector. If the size of the vector is smaller than the provided + * count, the vector is resized. */ +FLECS_API +int32_t _ecs_vector_set_count( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_count(vector, T, elem_count) \ + _ecs_vector_set_count(vector, ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_set_count_t(vector, size, alignment, elem_count) \ + _ecs_vector_set_count(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Return number of elements in vector. */ +FLECS_API +int32_t ecs_vector_count( + const ecs_vector_t *vector); + +/** Return size of vector. */ +FLECS_API +int32_t ecs_vector_size( + const ecs_vector_t *vector); + +/** Return first element of vector. */ +FLECS_API +void* _ecs_vector_first( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_first(vector, T) \ + ((T*)_ecs_vector_first(vector, ECS_VECTOR_T(T))) + +#define ecs_vector_first_t(vector, size, alignment) \ + _ecs_vector_first(vector, ECS_VECTOR_U(size, alignment)) + +/** Sort elements in vector. */ +FLECS_API +void _ecs_vector_sort( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + ecs_comparator_t compare_action); + +#define ecs_vector_sort(vector, T, compare_action) \ + _ecs_vector_sort(vector, ECS_VECTOR_T(T), compare_action) + +/** Return memory occupied by vector. */ +FLECS_API +void _ecs_vector_memory( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t *allocd, + int32_t *used); + +#define ecs_vector_memory(vector, T, allocd, used) \ + _ecs_vector_memory(vector, ECS_VECTOR_T(T), allocd, used) + +#define ecs_vector_memory_t(vector, size, alignment, allocd, used) \ + _ecs_vector_memory(vector, ECS_VECTOR_U(size, alignment), allocd, used) + +/** Copy vectors */ +FLECS_API +ecs_vector_t* _ecs_vector_copy( + const ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_copy(src, T) \ + _ecs_vector_copy(src, ECS_VECTOR_T(T)) + +#define ecs_vector_copy_t(src, size, alignment) \ + _ecs_vector_copy(src, ECS_VECTOR_U(size, alignment)) + +#ifndef FLECS_LEGACY +#define ecs_vector_each(vector, T, var, ...)\ + {\ + int var##_i, var##_count = ecs_vector_count(vector);\ + T* var##_array = ecs_vector_first(vector, T);\ + for (var##_i = 0; var##_i < var##_count; var##_i ++) {\ + T* var = &var##_array[var##_i];\ + __VA_ARGS__\ + }\ + } +#endif +#ifdef __cplusplus +} +#endif + + +/** C++ wrapper for vector class. */ +#ifdef __cplusplus +#ifndef FLECS_NO_CPP + +#include + +namespace flecs { + +template +class vector_iterator +{ +public: + explicit vector_iterator(T* value, int index) { + m_value = value; + m_index = index; + } + + bool operator!=(vector_iterator const& other) const + { + return m_index != other.m_index; + } + + T const& operator*() const + { + return m_value[m_index]; + } + + vector_iterator& operator++() + { + ++m_index; + return *this; + } + +private: + T* m_value; + int m_index; +}; + +template +class vector { +public: + explicit vector(ecs_vector_t *v) : m_vector( v ) { } + + vector(int32_t count = 0) : m_vector( nullptr ) { + if (count) { + init(count); + } + } + + vector(std::initializer_list elems) : m_vector( nullptr) { + init(elems.size()); + *this = elems; + } + + void operator=(std::initializer_list elems) { + for (auto elem : elems) { + this->add(elem); + } + } + + T& operator[](size_t index) { + return ecs_vector_get(m_vector, T, index)[0]; + } + + vector_iterator begin() { + return vector_iterator( + ecs_vector_first(m_vector, T), 0); + } + + vector_iterator end() { + return vector_iterator( + ecs_vector_last(m_vector, T), + ecs_vector_count(m_vector)); + } + + void clear() { + ecs_vector_clear(m_vector); + } + + void add(T& value) { + T* elem = ecs_vector_add(&m_vector, T); + *elem = value; + } + + void add(T&& value) { + T* elem = ecs_vector_add(&m_vector, T); + *elem = value; + } + + T& get(int32_t index) { + return ecs_vector_get(m_vector, T, index); + } + + T& first() { + return ecs_vector_first(m_vector, T); + } + + T& last() { + return ecs_vector_last(m_vector, T); + } + + int32_t count() { + return ecs_vector_count(m_vector); + } + + int32_t size() { + return ecs_vector_size(m_vector); + } + + ecs_vector_t *ptr() { + return m_vector; + } + + void ptr(ecs_vector_t *ptr) { + m_vector = ptr; + } + +private: + void init(int32_t count) { + m_vector = ecs_vector_new(T, count); + } + + ecs_vector_t *m_vector; +}; + +} + +#endif +#endif + +#endif +/** This is an implementation of a paged sparse set that stores the payload in + * the sparse array. + * + * A sparse set has a dense and a sparse array. The sparse array is directly + * indexed by a 64 bit identifier. The sparse element is linked with a dense + * element, which allows for liveliness checking. The liveliness check itself + * can be performed by doing (psuedo code): + * dense[sparse[sparse_id].dense] == sparse_id + * + * To ensure that the sparse array doesn't have to grow to a large size when + * using large sparse_id's, the sparse set uses paging. This cuts up the array + * into several pages of 4096 elements. When an element is set, the sparse set + * ensures that the corresponding page is created. The page associated with an + * id is determined by shifting a bit 12 bits to the right. + * + * The sparse set keeps track of a generation count per id, which is increased + * each time an id is deleted. The generation is encoded in the returned id. + * + * This sparse set implementation stores payload in the sparse array, which is + * not typical. The reason for this is to guarantee that (in combination with + * paging) the returned payload pointers are stable. This allows for various + * optimizations in the parts of the framework that uses the sparse set. + * + * The sparse set has been designed so that new ids can be generated in bulk, in + * an O(1) operation. The way this works is that once a dense-sparse pair is + * created, it is never unpaired. Instead it is moved to the end of the dense + * array, and the sparse set stores an additional count to keep track of the + * last alive id in the sparse set. To generate new ids in bulk, the sparse set + * only needs to increase this count by the number of requested ids. + */ + +#ifndef FLECS_SPARSE_H +#define FLECS_SPARSE_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_sparse_t ecs_sparse_t; + +/** Create new sparse set */ +FLECS_API ecs_sparse_t* _ecs_sparse_new( + ecs_size_t elem_size); + +#define ecs_sparse_new(type)\ + _ecs_sparse_new(sizeof(type)) + +/** Set id source. This allows the sparse set to use an external variable for + * issuing and increasing new ids. */ +FLECS_API void ecs_sparse_set_id_source( + ecs_sparse_t *sparse, + uint64_t *id_source); + +/** Free sparse set */ +FLECS_API void ecs_sparse_free( + ecs_sparse_t *sparse); + +/** Remove all elements from sparse set */ +FLECS_API void ecs_sparse_clear( + ecs_sparse_t *sparse); + +/** Add element to sparse set, this generates or recycles an id */ +FLECS_API void* _ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_add(sparse, type)\ + ((type*)_ecs_sparse_add(sparse, sizeof(type))) + +/** Get last issued id. */ +FLECS_API uint64_t ecs_sparse_last_id( + ecs_sparse_t *sparse); + +/** Generate or recycle a new id. */ +FLECS_API uint64_t ecs_sparse_new_id( + ecs_sparse_t *sparse); + +/** Generate or recycle new ids in bulk. The returned pointer points directly to + * the internal dense array vector with sparse ids. Operations on the sparse set + * can (and likely will) modify the contents of the buffer. */ +FLECS_API const uint64_t* ecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t count); + +/** Remove an element */ +FLECS_API void ecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t index); + +/** Remove an element, return pointer to the value in the sparse array */ +FLECS_API void* _ecs_sparse_remove_get( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t index); + +#define ecs_sparse_remove_get(sparse, type, index)\ + ((type*)_ecs_sparse_remove_get(sparse, sizeof(type), index)) + +/** Override the generation count for a specific id */ +FLECS_API void ecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t index); + +/** Check whether an id has ever been issued. */ +FLECS_API bool ecs_sparse_exists( + ecs_sparse_t *sparse, + uint64_t index); + +/** Test if id is alive, which requires the generation count tp match. */ +FLECS_API bool ecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index); + +/** Get value from sparse set by dense id. This function is useful in + * combination with ecs_sparse_count for iterating all values in the set. */ +FLECS_API void* _ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index); + +#define ecs_sparse_get(sparse, type, index)\ + ((type*)_ecs_sparse_get(sparse, sizeof(type), index)) + +/** Get the number of alive elements in the sparse set. */ +FLECS_API int32_t ecs_sparse_count( + const ecs_sparse_t *sparse); + +/** Return total number of allocated elements in the dense array */ +FLECS_API int32_t ecs_sparse_size( + const ecs_sparse_t *sparse); + +/** Get element by (sparse) id. The returned pointer is stable for the duration + * of the sparse set, as it is stored in the sparse array. */ +FLECS_API void* _ecs_sparse_get_sparse( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t index); + +#define ecs_sparse_get_sparse(sparse, type, index)\ + ((type*)_ecs_sparse_get_sparse(sparse, sizeof(type), index)) + +/** Like get_sparse, but don't care whether element is alive or not. */ +FLECS_API void* _ecs_sparse_get_sparse_any( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t index); + +#define ecs_sparse_get_sparse_any(sparse, type, index)\ + ((type*)_ecs_sparse_get_sparse_any(sparse, sizeof(type), index)) + +/** Get or create element by (sparse) id. */ +FLECS_API void* _ecs_sparse_get_or_create( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t index); + +#define ecs_sparse_get_or_create(sparse, type, index)\ + ((type*)_ecs_sparse_get_or_create(sparse, sizeof(type), index)) + +/** Set value. */ +FLECS_API void* _ecs_sparse_set( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t index, + void *value); + +#define ecs_sparse_set(sparse, type, index, value)\ + ((type*)_ecs_sparse_set(sparse, sizeof(type), index, value)) + +/** Get pointer to ids (alive and not alive). Use with count() or size(). */ +FLECS_API const uint64_t* ecs_sparse_ids( + const ecs_sparse_t *sparse); + +/** Set size of the dense array. */ +FLECS_API void ecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count); + +/** Copy sparse set into a new sparse set. */ +FLECS_API ecs_sparse_t* ecs_sparse_copy( + const ecs_sparse_t *src); + +/** Restore sparse set into destination sparse set. */ +FLECS_API void ecs_sparse_restore( + ecs_sparse_t *dst, + const ecs_sparse_t *src); + +/** Get memory usage of sparse set. */ +FLECS_API void ecs_sparse_memory( + ecs_sparse_t *sparse, + int32_t *allocd, + int32_t *used); + +#ifndef FLECS_LEGACY +#define ecs_sparse_each(sparse, T, var, ...)\ + {\ + int var##_i, var##_count = ecs_sparse_count(sparse);\ + for (var##_i = 0; var##_i < var##_count; var##_i ++) {\ + T* var = ecs_sparse_get(sparse, T, var##_i);\ + __VA_ARGS__\ + }\ + } +#endif + +#ifdef __cplusplus +} +#endif + +#endif +/** Simple bitset implementation. The bitset allows for storage of arbitrary + * numbers of bits. + */ + +#ifndef FLECS_BITSET_H +#define FLECS_BITSET_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_bitset_t { + uint64_t *data; + int32_t count; + ecs_size_t size; +} ecs_bitset_t; + +/** Initialize bitset. */ +void ecs_bitset_init( + ecs_bitset_t *bs); + +/** Deinialize bitset. */ +void ecs_bitset_deinit( + ecs_bitset_t *bs); + +/** Add n elements to bitset. */ +void ecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count); + +/** Ensure element exists. */ +void ecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count); + +/** Set element. */ +void ecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value); + +/** Get element. */ +bool ecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem); + +/** Return number of elements. */ +int32_t ecs_bitset_count( + const ecs_bitset_t *bs); + +/** Remove from bitset. */ +void ecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem); + +/** Swap values in bitset. */ +void ecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b); + +#ifdef __cplusplus +} +#endif + +#endif +/** Key-value datastructure. The map allows for fast retrieval of a payload for + * a 64-bit key. While it is not as fast as the sparse set, it is better at + * handling randomly distributed values. + * + * Payload is stored in bucket arrays. A bucket is computed from an id by + * using the (bucket_count - 1) as an AND-mask. The number of buckets is always + * a power of 2. Multiple keys will be stored in the same bucket. As a result + * the worst case retrieval performance of the map is O(n), though this is rare. + * On average lookup performance should equal O(1). + * + * The datastructure will automatically grow the number of buckets when the + * ratio between elements and buckets exceeds a certain threshold (LOAD_FACTOR). + * + * Note that while the implementation is a hashmap, it can only compute hashes + * for the provided 64 bit keys. This means that the provided keys must always + * be unique. If the provided keys are hashes themselves, it is the + * responsibility of the user to ensure that collisions are handled. + * + * In debug mode the map verifies that the type provided to the map functions + * matches the one used at creation time. + */ + +#ifndef FLECS_MAP_H +#define FLECS_MAP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_map_t ecs_map_t; +typedef struct ecs_bucket_t ecs_bucket_t; +typedef uint64_t ecs_map_key_t; + +typedef struct ecs_map_iter_t { + const ecs_map_t *map; + ecs_bucket_t *bucket; + int32_t bucket_index; + int32_t element_index; + void *payload; +} ecs_map_iter_t; + +/** Create new map. */ +FLECS_API +ecs_map_t * _ecs_map_new( + ecs_size_t elem_size, + ecs_size_t alignment, + int32_t elem_count); + +#define ecs_map_new(T, elem_count)\ + _ecs_map_new(sizeof(T), ECS_ALIGNOF(T), elem_count) + +/** Get element for key, returns NULL if they key doesn't exist. */ +FLECS_API +void * _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +#define ecs_map_get(map, T, key)\ + (T*)_ecs_map_get(map, sizeof(T), (ecs_map_key_t)key) + +/** Get pointer element. This dereferences the map element as a pointer. This + * operation returns NULL when either the element does not exist or whether the + * pointer is NULL, and should therefore only be used when the application knows + * for sure that a pointer should never be NULL. */ +FLECS_API +void * _ecs_map_get_ptr( + const ecs_map_t *map, + ecs_map_key_t key); + +#define ecs_map_get_ptr(map, T, key)\ + (T)_ecs_map_get_ptr(map, key) + +/** Get or create element for key. */ +FLECS_API +void * _ecs_map_ensure( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +#define ecs_map_ensure(map, T, key)\ + (T*)_ecs_map_ensure(map, sizeof(T), (ecs_map_key_t)key) + +/** Set element. */ +FLECS_API +void* _ecs_map_set( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload); + +#define ecs_map_set(map, key, payload)\ + _ecs_map_set(map, sizeof(*payload), (ecs_map_key_t)key, payload); + +/** Free map. */ +FLECS_API +void ecs_map_free( + ecs_map_t *map); + +/** Remove key from map. */ +FLECS_API +void ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key); + +/** Remove all elements from map. */ +FLECS_API +void ecs_map_clear( + ecs_map_t *map); + +/** Return number of elements in map. */ +FLECS_API +int32_t ecs_map_count( + const ecs_map_t *map); + +/** Return number of buckets in map. */ +FLECS_API +int32_t ecs_map_bucket_count( + const ecs_map_t *map); + +/** Return iterator to map contents. */ +FLECS_API +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map); + +/** Obtain next element in map from iterator. */ +FLECS_API +void* _ecs_map_next( + ecs_map_iter_t* iter, + ecs_size_t elem_size, + ecs_map_key_t *key); + +#define ecs_map_next(iter, T, key) \ + (T*)_ecs_map_next(iter, sizeof(T), key) + +/** Obtain next pointer element from iterator. See ecs_map_get_ptr. */ +FLECS_API +void* _ecs_map_next_ptr( + ecs_map_iter_t* iter, + ecs_map_key_t *key); + +#define ecs_map_next_ptr(iter, T, key) \ + (T)_ecs_map_next_ptr(iter, key) + +/** Grow number of buckets in the map for specified number of elements. */ +FLECS_API +void ecs_map_grow( + ecs_map_t *map, + int32_t elem_count); + +/** Set number of buckets in the map for specified number of elements. */ +FLECS_API +void ecs_map_set_size( + ecs_map_t *map, + int32_t elem_count); + +/** Return memory occupied by map. */ +FLECS_API +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used); + +#ifndef FLECS_LEGACY +#define ecs_map_each(map, T, key, var, ...)\ + {\ + ecs_map_iter_t it = ecs_map_iter(map);\ + ecs_map_key_t key;\ + T* var;\ + (void)key;\ + (void)var;\ + while ((var = ecs_map_next(&it, T, &key))) {\ + __VA_ARGS__\ + }\ + } +#endif +#ifdef __cplusplus +} +#endif + +/** C++ wrapper for map. */ +#ifdef __cplusplus +#ifndef FLECS_NO_CPP + +#include + +namespace flecs { + +template +class map { +public: + map(int32_t count = 0) { + init(count); + } + + map(std::initializer_list> elems) { + init(elems.size()); + *this = elems; + } + + void operator=(std::initializer_list> elems) { + for (auto elem : elems) { + this->set(elem.first, elem.second); + } + } + + void clear() { + ecs_map_clear(m_map); + } + + int32_t count() { + return ecs_map_count(m_map); + } + + void set(K& key, T& value) { + ecs_map_set(m_map, reinterpret_cast(key), &value); + } + + T& get(K& key) { + *(T*)ecs_map_get(m_map, T, reinterpret_cast(key)); + } + +private: + void init(int32_t count) { + m_map = ecs_map_new(T, count); + } + + ecs_map_t *m_map; +}; + +} + +#endif +#endif + +#endif +/* Datastructure that stores N interleaved linked lists in an array. + * This allows for efficient storage of elements with mutually exclusive values. + * Each linked list has a header element which points to the index in the array + * that stores the first node of the list. Each list node points to the next + * array element. + * + * The datastructure needs to be created with min and max values, so that it can + * allocate an array of headers that can be directly indexed by the value. The + * values are stored in a contiguous array, which allows for the values to be + * iterated without having to follow the linked list nodes. + * + * The datastructure allows for efficient storage and retrieval for values with + * mutually exclusive values, such as enumeration values. The linked list allows + * an application to obtain all elements for a given (enumeration) value without + * having to search. + * + * While the list accepts 64 bit values, it only uses the lower 32bits of the + * value for selecting the correct linked list. + */ + +#ifndef FLECS_SWITCH_LIST_H +#define FLECS_SWITCH_LIST_H + + +typedef struct ecs_switch_header_t { + int32_t element; /* First element for value */ + int32_t count; /* Number of elements for value */ +} ecs_switch_header_t; + +typedef struct ecs_switch_node_t { + int32_t next; /* Next node in list */ + int32_t prev; /* Prev node in list */ +} ecs_switch_node_t; + +typedef struct ecs_switch_t { + uint64_t min; /* Minimum value the switch can store */ + uint64_t max; /* Maximum value the switch can store */ + ecs_switch_header_t *headers; /* Array with headers, indexed by value */ + ecs_vector_t *nodes; /* Vector with nodes, of type ecs_switch_node_t */ + ecs_vector_t *values; /* Vector with values, of type uint64_t */ +} ecs_switch_t; + +/** Create new switch. */ +ecs_switch_t* ecs_switch_new( + uint64_t min, + uint64_t max, + int32_t elements); + +/** Free switch. */ +void ecs_switch_free( + ecs_switch_t *sw); + +/** Add element to switch, initialize value to 0 */ +void ecs_switch_add( + ecs_switch_t *sw); + +/** Set number of elements in switch list */ +void ecs_switch_set_count( + ecs_switch_t *sw, + int32_t count); + +/** Ensure that element exists. */ +void ecs_switch_ensure( + ecs_switch_t *sw, + int32_t count); + +/** Add n elements. */ +void ecs_switch_addn( + ecs_switch_t *sw, + int32_t count); + +/** Set value of element. */ +void ecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value); + +/** Remove element. */ +void ecs_switch_remove( + ecs_switch_t *sw, + int32_t element); + +/** Get value for element. */ +uint64_t ecs_switch_get( + const ecs_switch_t *sw, + int32_t element); + +/** Swap element. */ +void ecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2); + +/** Get vector with all values. Use together with count(). */ +ecs_vector_t* ecs_switch_values( + const ecs_switch_t *sw); + +/** Return number of different values. */ +int32_t ecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value); + +/** Return first element for value. */ +int32_t ecs_switch_first( + const ecs_switch_t *sw, + uint64_t value); + +/** Return next element for value. Use with first(). */ +int32_t ecs_switch_next( + const ecs_switch_t *sw, + int32_t elem); + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif +#ifndef FLECS_STRBUF_H_ +#define FLECS_STRBUF_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_STRBUF_INIT (ecs_strbuf_t){0} +#define ECS_STRBUF_ELEMENT_SIZE (511) +#define ECS_STRBUF_MAX_LIST_DEPTH (32) + +/* A buffer builds up a list of elements which individually can be up to N bytes + * large. While appending, data is added to these elements. More elements are + * added on the fly when needed. When an application calls ecs_strbuf_get, all + * elements are combined in one string and the element administration is freed. + * + * This approach prevents reallocs of large blocks of memory, and therefore + * copying large blocks of memory when appending to a large buffer. A buffer + * preallocates some memory for the element overhead so that for small strings + * there is hardly any overhead, while for large strings the overhead is offset + * by the reduced time spent on copying memory. + */ + +typedef struct ecs_strbuf_element { + bool buffer_embedded; + int32_t pos; + char *buf; + struct ecs_strbuf_element *next; +} ecs_strbuf_element; + +typedef struct ecs_strbuf_element_embedded { + ecs_strbuf_element super; + char buf[ECS_STRBUF_ELEMENT_SIZE + 1]; +} ecs_strbuf_element_embedded; + +typedef struct ecs_strbuf_element_str { + ecs_strbuf_element super; + char *alloc_str; +} ecs_strbuf_element_str; + +typedef struct ecs_strbuf_list_elem { + int32_t count; + const char *separator; +} ecs_strbuf_list_elem; + +typedef struct ecs_strbuf_t { + /* When set by an application, append will write to this buffer */ + char *buf; + + /* The maximum number of characters that may be printed */ + int32_t max; + + /* Size of elements minus current element */ + int32_t size; + + /* The number of elements in use */ + int32_t elementCount; + + /* Always allocate at least one element */ + ecs_strbuf_element_embedded firstElement; + + /* The current element being appended to */ + ecs_strbuf_element *current; + + /* Stack that keeps track of number of list elements, used for conditionally + * inserting a separator */ + ecs_strbuf_list_elem list_stack[ECS_STRBUF_MAX_LIST_DEPTH]; + int32_t list_sp; +} ecs_strbuf_t; + +/* Append format string to a buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...); + +/* Append format string with argument list to a buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_vappend( + ecs_strbuf_t *buffer, + const char *fmt, + va_list args); + +/* Append string to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +/* Append source buffer to destination buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer); + +/* Append string to buffer, transfer ownership to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *buffer, + char *str); + +/* Append string to buffer, do not free/modify string. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *buffer, + const char *str); + +/* Append n characters to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstrn( + ecs_strbuf_t *buffer, + const char *str, + int32_t n); + +/* Return result string (also resets buffer) */ +FLECS_API +char *ecs_strbuf_get( + ecs_strbuf_t *buffer); + +/* Reset buffer without returning a string */ +FLECS_API +void ecs_strbuf_reset( + ecs_strbuf_t *buffer); + +/* Push a list */ +FLECS_API +void ecs_strbuf_list_push( + ecs_strbuf_t *buffer, + const char *list_open, + const char *separator); + +/* Pop a new list */ +FLECS_API +void ecs_strbuf_list_pop( + ecs_strbuf_t *buffer, + const char *list_close); + +/* Insert a new element in list */ +FLECS_API +void ecs_strbuf_list_next( + ecs_strbuf_t *buffer); + +/* Append formatted string as a new element in list */ +FLECS_API +bool ecs_strbuf_list_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...); + +/* Append string as a new element in list */ +FLECS_API +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +#ifdef __cplusplus +} +#endif + +#endif +#ifndef FLECS_OS_API_H +#define FLECS_OS_API_H + +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#elif defined(__FreeBSD__) +#include +#else +#include +#endif + +#if defined(_WIN32) +#define ECS_OS_WINDOWS +#elif defined(__linux__) +#define ECS_OS_LINUX +#elif defined(__APPLE__) && defined(__MACH__) +#define ECS_OS_DARWIN +#else +/* Unknown OS */ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_time_t { + uint32_t sec; + uint32_t nanosec; +} ecs_time_t; + +/* Allocation counters (not thread safe) */ +extern int64_t ecs_os_api_malloc_count; +extern int64_t ecs_os_api_realloc_count; +extern int64_t ecs_os_api_calloc_count; +extern int64_t ecs_os_api_free_count; + +/* Use handle types that _at least_ can store pointers */ +typedef uintptr_t ecs_os_thread_t; +typedef uintptr_t ecs_os_cond_t; +typedef uintptr_t ecs_os_mutex_t; +typedef uintptr_t ecs_os_dl_t; + +/* Generic function pointer type */ +typedef void (*ecs_os_proc_t)(void); + +/* OS API init */ +typedef +void (*ecs_os_api_init_t)(void); + +/* OS API deinit */ +typedef +void (*ecs_os_api_fini_t)(void); + +/* Memory management */ +typedef +void* (*ecs_os_api_malloc_t)( + ecs_size_t size); + +typedef +void (*ecs_os_api_free_t)( + void *ptr); + +typedef +void* (*ecs_os_api_realloc_t)( + void *ptr, + ecs_size_t size); + +typedef +void* (*ecs_os_api_calloc_t)( + ecs_size_t size); + +typedef +char* (*ecs_os_api_strdup_t)( + const char *str); + +/* Threads */ +typedef +void* (*ecs_os_thread_callback_t)( + void*); + +typedef +ecs_os_thread_t (*ecs_os_api_thread_new_t)( + ecs_os_thread_callback_t callback, + void *param); + +typedef +void* (*ecs_os_api_thread_join_t)( + ecs_os_thread_t thread); + + +/* Atomic increment / decrement */ +typedef +int (*ecs_os_api_ainc_t)( + int32_t *value); + + +/* Mutex */ +typedef +ecs_os_mutex_t (*ecs_os_api_mutex_new_t)( + void); + +typedef +void (*ecs_os_api_mutex_lock_t)( + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_mutex_unlock_t)( + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_mutex_free_t)( + ecs_os_mutex_t mutex); + +/* Condition variable */ +typedef +ecs_os_cond_t (*ecs_os_api_cond_new_t)( + void); + +typedef +void (*ecs_os_api_cond_free_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_signal_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_broadcast_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_wait_t)( + ecs_os_cond_t cond, + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_sleep_t)( + int32_t sec, + int32_t nanosec); + +typedef +void (*ecs_os_api_get_time_t)( + ecs_time_t *time_out); + +/* Logging */ +typedef +void (*ecs_os_api_log_t)( + const char *fmt, + va_list args); + +/* Application termination */ +typedef +void (*ecs_os_api_abort_t)( + void); + +/* Dynamic libraries */ +typedef +ecs_os_dl_t (*ecs_os_api_dlopen_t)( + const char *libname); + +typedef +ecs_os_proc_t (*ecs_os_api_dlproc_t)( + ecs_os_dl_t lib, + const char *procname); + +typedef +void (*ecs_os_api_dlclose_t)( + ecs_os_dl_t lib); + +typedef +char* (*ecs_os_api_module_to_path_t)( + const char *module_id); + +/* Prefix members of struct with 'ecs_' as some system headers may define + * macro's for functions like "strdup", "log" or "_free" */ + +typedef struct ecs_os_api_t { + /* API init / deinit */ + ecs_os_api_init_t init_; + ecs_os_api_fini_t fini_; + + /* Memory management */ + ecs_os_api_malloc_t malloc_; + ecs_os_api_realloc_t realloc_; + ecs_os_api_calloc_t calloc_; + ecs_os_api_free_t free_; + + /* Strings */ + ecs_os_api_strdup_t strdup_; + + /* Threads */ + ecs_os_api_thread_new_t thread_new_; + ecs_os_api_thread_join_t thread_join_; + + /* Atomic incremenet / decrement */ + ecs_os_api_ainc_t ainc_; + ecs_os_api_ainc_t adec_; + + /* Mutex */ + ecs_os_api_mutex_new_t mutex_new_; + ecs_os_api_mutex_free_t mutex_free_; + ecs_os_api_mutex_lock_t mutex_lock_; + ecs_os_api_mutex_lock_t mutex_unlock_; + + /* Condition variable */ + ecs_os_api_cond_new_t cond_new_; + ecs_os_api_cond_free_t cond_free_; + ecs_os_api_cond_signal_t cond_signal_; + ecs_os_api_cond_broadcast_t cond_broadcast_; + ecs_os_api_cond_wait_t cond_wait_; + + /* Time */ + ecs_os_api_sleep_t sleep_; + ecs_os_api_get_time_t get_time_; + + /* Logging */ + ecs_os_api_log_t log_; + ecs_os_api_log_t log_error_; + ecs_os_api_log_t log_debug_; + ecs_os_api_log_t log_warning_; + + /* Application termination */ + ecs_os_api_abort_t abort_; + + /* Dynamic library loading */ + ecs_os_api_dlopen_t dlopen_; + ecs_os_api_dlproc_t dlproc_; + ecs_os_api_dlclose_t dlclose_; + + /* Overridable function that translates from a logical module id to a + * shared library filename */ + ecs_os_api_module_to_path_t module_to_dl_; + + /* Overridable function that translates from a logical module id to a + * path that contains module-specif resources or assets */ + ecs_os_api_module_to_path_t module_to_etc_; +} ecs_os_api_t; + +FLECS_API +extern ecs_os_api_t ecs_os_api; + +FLECS_API +void ecs_os_init(void); + +FLECS_API +void ecs_os_fini(void); + +FLECS_API +void ecs_os_set_api( + ecs_os_api_t *os_api); + +FLECS_API +void ecs_os_set_api_defaults(void); + +/* Memory management */ +#define ecs_os_malloc(size) ecs_os_api.malloc_(size); +#define ecs_os_free(ptr) ecs_os_api.free_(ptr); +#define ecs_os_realloc(ptr, size) ecs_os_api.realloc_(ptr, size) +#define ecs_os_calloc(size) ecs_os_api.calloc_(size) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define ecs_os_alloca(size) _alloca((size_t)(size)) +#else +#define ecs_os_alloca(size) alloca((size_t)(size)) +#endif + +/* Strings */ +#define ecs_os_strdup(str) ecs_os_api.strdup_(str) +#define ecs_os_strlen(str) (ecs_size_t)strlen(str) +#define ecs_os_strcmp(str1, str2) strcmp(str1, str2) +#define ecs_os_strncmp(str1, str2, num) strncmp(str1, str2, (size_t)(num)) +#define ecs_os_memcmp(ptr1, ptr2, num) memcmp(ptr1, ptr2, (size_t)(num)) +#define ecs_os_memcpy(ptr1, ptr2, num) memcpy(ptr1, ptr2, (size_t)(num)) +#define ecs_os_memset(ptr, value, num) memset(ptr, value, (size_t)(num)) +#define ecs_os_memmove(ptr, value, num) memmove(ptr, value, (size_t)(num)) + +#if defined(_MSC_VER) +#define ecs_os_strcat(str1, str2) strcat_s(str1, INT_MAX, str2) +#define ecs_os_sprintf(ptr, ...) sprintf_s(ptr, INT_MAX, __VA_ARGS__) +#define ecs_os_vsprintf(ptr, fmt, args) vsprintf_s(ptr, INT_MAX, fmt, args) +#define ecs_os_strcpy(str1, str2) strcpy_s(str1, INT_MAX, str2) +#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, (size_t)(num)) +#else +#define ecs_os_strcat(str1, str2) strcat(str1, str2) +#define ecs_os_sprintf(ptr, ...) sprintf(ptr, __VA_ARGS__) +#define ecs_os_vsprintf(ptr, fmt, args) vsprintf(ptr, fmt, args) +#define ecs_os_strcpy(str1, str2) strcpy(str1, str2) +#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, (size_t)(num)) +#endif + + +/* Threads */ +#define ecs_os_thread_new(callback, param) ecs_os_api.thread_new_(callback, param) +#define ecs_os_thread_join(thread) ecs_os_api.thread_join_(thread) + +/* Atomic increment / decrement */ +#define ecs_os_ainc(value) ecs_os_api.ainc_(value) +#define ecs_os_adec(value) ecs_os_api.adec_(value) + +/* Mutex */ +#define ecs_os_mutex_new() ecs_os_api.mutex_new_() +#define ecs_os_mutex_free(mutex) ecs_os_api.mutex_free_(mutex) +#define ecs_os_mutex_lock(mutex) ecs_os_api.mutex_lock_(mutex) +#define ecs_os_mutex_unlock(mutex) ecs_os_api.mutex_unlock_(mutex) + +/* Condition variable */ +#define ecs_os_cond_new() ecs_os_api.cond_new_() +#define ecs_os_cond_free(cond) ecs_os_api.cond_free_(cond) +#define ecs_os_cond_signal(cond) ecs_os_api.cond_signal_(cond) +#define ecs_os_cond_broadcast(cond) ecs_os_api.cond_broadcast_(cond) +#define ecs_os_cond_wait(cond, mutex) ecs_os_api.cond_wait_(cond, mutex) + +/* Time */ +#define ecs_os_sleep(sec, nanosec) ecs_os_api.sleep_(sec, nanosec) +#define ecs_os_get_time(time_out) ecs_os_api.get_time_(time_out) + +/* Logging (use functions to avoid using variadic macro arguments) */ +FLECS_API +void ecs_os_log(const char *fmt, ...); + +FLECS_API +void ecs_os_warn(const char *fmt, ...); + +FLECS_API +void ecs_os_err(const char *fmt, ...); + +FLECS_API +void ecs_os_dbg(const char *fmt, ...); + +/* Application termination */ +#define ecs_os_abort() ecs_os_api.abort_() + +/* Dynamic libraries */ +#define ecs_os_dlopen(libname) ecs_os_api.dlopen_(libname) +#define ecs_os_dlproc(lib, procname) ecs_os_api.dlproc_(lib, procname) +#define ecs_os_dlclose(lib) ecs_os_api.dlclose_(lib) + +/* Module id translation */ +#define ecs_os_module_to_dl(lib) ecs_os_api.module_to_dl_(lib) +#define ecs_os_module_to_etc(lib) ecs_os_api.module_to_etc_(lib) + +/* Sleep with floating point time */ +FLECS_API +void ecs_sleepf( + double t); + +/* Measure time since provided timestamp */ +FLECS_API +double ecs_time_measure( + ecs_time_t *start); + +/* Calculate difference between two timestamps */ +FLECS_API +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2); + +/* Convert time value to a double */ +FLECS_API +double ecs_time_to_double( + ecs_time_t t); + +FLECS_API +void* ecs_os_memdup( + const void *src, + ecs_size_t size); + +/** Are heap functions available? */ +FLECS_API +bool ecs_os_has_heap(void); + +/** Are threading functions available? */ +FLECS_API +bool ecs_os_has_threading(void); + +/** Are time functions available? */ +FLECS_API +bool ecs_os_has_time(void); + +/** Are logging functions available? */ +FLECS_API +bool ecs_os_has_logging(void); + +/** Are dynamic library functions available? */ +FLECS_API +bool ecs_os_has_dl(void); + +/** Are module path functions available? */ +FLECS_API +bool ecs_os_has_modules(void); + +#ifdef __cplusplus +} +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup api_types Basic API types + * @{ + */ + +/** An entity identifier. */ +typedef uint64_t ecs_entity_t; + +/** A vector containing component identifiers used to describe a type. */ +typedef const ecs_vector_t* ecs_type_t; + +/** An ECS world is the container for all ECS data and supporting features. */ +typedef struct ecs_world_t ecs_world_t; + +/** A snapshot stores the state of a world in a particular point in time. */ +typedef struct ecs_snapshot_t ecs_snapshot_t; + +/** Queries are the primary mechanism for iterating (prematched) entities. */ +typedef struct ecs_query_t ecs_query_t; + +/* An iterator lets an application iterate entities across tables. */ +typedef struct ecs_iter_t ecs_iter_t; + +/** Refs cache data that lets them access components faster than ecs_get. */ +typedef struct ecs_ref_t ecs_ref_t; + +/** Describes how a filter should match components with a table. */ +typedef enum ecs_match_kind_t { + EcsMatchDefault = 0, + EcsMatchAll, /**< Match all components in a type */ + EcsMatchAny, /**< Match one of the components in a type */ + EcsMatchExact /**< Match the type exactly */ +} ecs_match_kind_t; + +/** Filters alllow for ad-hoc quick filtering of entity tables. */ +typedef struct ecs_filter_t { + ecs_type_t include; /**< Components that should match */ + ecs_type_t exclude; /**< Components that should not match */ + ecs_match_kind_t include_kind; /**< Match kind for include components */ + ecs_match_kind_t exclude_kind; /**< Match kind for exclude components */ +} ecs_filter_t; + +/** Type that contains information about the world. */ +typedef struct ecs_world_info_t { + ecs_entity_t last_component_id; /**< Last issued component entity id */ + ecs_entity_t last_id; /**< Last issued entity id */ + ecs_entity_t min_id; /**< First allowed entity id */ + ecs_entity_t max_id; /**< Last allowed entity id */ + + FLECS_FLOAT delta_time_raw; /**< Raw delta time (no time scaling) */ + FLECS_FLOAT delta_time; /**< Time passed to or computed by ecs_progress */ + FLECS_FLOAT time_scale; /**< Time scale applied to delta_time */ + FLECS_FLOAT target_fps; /**< Target fps */ + FLECS_FLOAT frame_time_total; /**< Total time spent processing a frame */ + FLECS_FLOAT system_time_total; /**< Total time spent in systems */ + FLECS_FLOAT merge_time_total; /**< Total time spent in merges */ + FLECS_FLOAT world_time_total; /**< Time elapsed in simulation */ + FLECS_FLOAT world_time_total_raw; /**< Time elapsed in simulation (no scaling) */ + FLECS_FLOAT sleep_err; /**< Measured sleep error */ + + int32_t frame_count_total; /**< Total number of frames */ + int32_t merge_count_total; /**< Total number of merges */ + int32_t pipeline_build_count_total; /**< Total number of pipeline builds */ + int32_t systems_ran_frame; /**< Total number of systems ran in last frame */ +} ecs_world_info_t; + +/** @} */ + +/** + * @defgroup function_types Function Types + * @{ + */ + +/** Action callback for systems and triggers */ +typedef void (*ecs_iter_action_t)( + ecs_iter_t *it); + +typedef bool (*ecs_iter_next_action_t)( + ecs_iter_t *it); + +/** Compare callback used for sorting */ +typedef int (*ecs_compare_action_t)( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); + +/** Compare callback used for sorting */ +typedef int32_t (*ecs_rank_type_action_t)( + ecs_world_t *world, + ecs_entity_t rank_component, + ecs_type_t type); + +/** Initialization action for modules */ +typedef void (*ecs_module_action_t)( + ecs_world_t *world); + +/** Action callback on world exit */ +typedef void (*ecs_fini_action_t)( + ecs_world_t *world, + void *ctx); + +/** + * @file api_defines.h + * @brief Supporting types for the public API. + * + * This file containstypes that are typically not used by an application but + * support the public API, and therefore must be exposed. This header should not + * be included by itself. + */ + +#ifndef FLECS_API_TYPES_H +#define FLECS_API_TYPES_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Opaque types +//////////////////////////////////////////////////////////////////////////////// + +/** A stage enables modification while iterating and from multiple threads */ +typedef struct ecs_stage_t ecs_stage_t; + +/** A table is where entities and components are stored */ +typedef struct ecs_table_t ecs_table_t; + +/** A record stores data to map an entity id to a location in a table */ +typedef struct ecs_record_t ecs_record_t; + +/** Table column */ +typedef struct ecs_column_t ecs_column_t; + +/** Table data */ +typedef struct ecs_data_t ecs_data_t; + + +//////////////////////////////////////////////////////////////////////////////// +//// Non-opaque types +//////////////////////////////////////////////////////////////////////////////// + +struct ecs_record_t { + ecs_table_t *table; /* Identifies a type (and table) in world */ + int32_t row; /* Table row of the entity */ +}; + +/** Cached reference. */ +struct ecs_ref_t { + ecs_entity_t entity; /**< Entity of the reference */ + ecs_entity_t component; /**< Component of the reference */ + void *table; /**< Last known table */ + int32_t row; /**< Last known location in table */ + int32_t alloc_count; /**< Last known alloc count of table */ + ecs_record_t *record; /**< Pointer to record, if in main stage */ + const void *ptr; /**< Cached ptr */ +}; + +/** Array of entity ids that, other than a type, can live on the stack */ +typedef struct ecs_entities_t { + ecs_entity_t *array; /**< An array with entity ids */ + int32_t count; /**< The number of entities in the array */ +} ecs_entities_t; + +typedef struct ecs_page_cursor_t { + int32_t first; + int32_t count; +} ecs_page_cursor_t; + +typedef struct ecs_page_iter_t { + int32_t offset; + int32_t limit; + int32_t remaining; +} ecs_page_iter_t; + +/** Table specific data for iterators */ +typedef struct ecs_iter_table_t { + int32_t *columns; /**< Mapping from query columns to table columns */ + ecs_table_t *table; /**< The current table. */ + ecs_data_t *data; /**< Table component data */ + ecs_entity_t *components; /**< Components in current table */ + ecs_type_t *types; /**< Components in current table */ + ecs_ref_t *references; /**< References to entities (from query) */ +} ecs_iter_table_t; + +/** Scope-iterator specific data */ +typedef struct ecs_scope_iter_t { + ecs_filter_t filter; + ecs_vector_t *tables; + int32_t index; + ecs_iter_table_t table; +} ecs_scope_iter_t; + +/** Filter-iterator specific data */ +typedef struct ecs_filter_iter_t { + ecs_filter_t filter; + ecs_sparse_t *tables; + int32_t index; + ecs_iter_table_t table; +} ecs_filter_iter_t; + +/** Iterator flags used to quickly select the optimal iterator algorithm */ +typedef enum ecs_query_iter_kind_t { + EcsQuerySimpleIter, /**< No paging, sorting or sparse columns */ + EcsQueryPagedIter, /**< Regular iterator with paging */ + EcsQuerySortedIter, /**< Sorted iterator */ + EcsQuerySwitchIter /**< Switch type iterator */ +} ecs_query_iter_kind_t; + +/** Query-iterator specific data */ +typedef struct ecs_query_iter_t { + ecs_page_iter_t page_iter; + int32_t index; + int32_t sparse_smallest; + int32_t sparse_first; + int32_t bitset_first; +} ecs_query_iter_t; + +/** Query-iterator specific data */ +typedef struct ecs_snapshot_iter_t { + ecs_filter_t filter; + ecs_vector_t *tables; /* ecs_table_leaf_t */ + int32_t index; + ecs_iter_table_t table; +} ecs_snapshot_iter_t; + +/** The ecs_iter_t struct allows applications to iterate tables. + * Queries and filters, among others, allow an application to iterate entities + * that match a certain set of components. Because of how data is stored + * internally, entities with a given set of components may be stored in multiple + * consecutive arrays, stored across multiple tables. The ecs_iter_t type + * enables iteration across tables. */ +struct ecs_iter_t { + ecs_world_t *world; /**< The world */ + ecs_world_t *real_world; /**< Actual world. This differs from world when using threads. */ + ecs_entity_t system; /**< The current system (if applicable) */ + ecs_query_iter_kind_t kind; + + ecs_iter_table_t *table; /**< Table related data */ + ecs_query_t *query; /**< Current query being evaluated */ + int32_t table_count; /**< Active table count for query */ + int32_t inactive_table_count; /**< Inactive table count for query */ + int32_t column_count; /**< Number of columns for system */ + + void *table_columns; /**< Table component data */ + ecs_entity_t *entities; /**< Entity identifiers */ + + void *param; /**< User data (EcsContext or param argument) */ + FLECS_FLOAT delta_time; /**< Time elapsed since last frame */ + FLECS_FLOAT delta_system_time;/**< Time elapsed since last system invocation */ + FLECS_FLOAT world_time; /**< Time elapsed since start of simulation */ + + int32_t frame_offset; /**< Offset relative to frame */ + int32_t offset; /**< Offset relative to current table */ + int32_t count; /**< Number of entities to process by system */ + int32_t total_count; /**< Total number of entities in table */ + + ecs_entities_t *triggered_by; /**< Component(s) that triggered the system */ + ecs_entity_t interrupted_by; /**< When set, system execution is interrupted */ + + union { + ecs_scope_iter_t parent; + ecs_filter_iter_t filter; + ecs_query_iter_t query; + ecs_snapshot_iter_t snapshot; + } iter; /**< Iterator specific data */ +}; + +typedef enum EcsMatchFailureReason { + EcsMatchOk, + EcsMatchNotASystem, + EcsMatchSystemIsATask, + EcsMatchEntityIsDisabled, + EcsMatchEntityIsPrefab, + EcsMatchFromSelf, + EcsMatchFromOwned, + EcsMatchFromShared, + EcsMatchFromContainer, + EcsMatchFromEntity, + EcsMatchOrFromSelf, + EcsMatchOrFromOwned, + EcsMatchOrFromShared, + EcsMatchOrFromContainer, + EcsMatchNotFromSelf, + EcsMatchNotFromOwned, + EcsMatchNotFromShared, + EcsMatchNotFromContainer, +} EcsMatchFailureReason; + +typedef struct ecs_match_failure_t { + EcsMatchFailureReason reason; + int32_t column; +} ecs_match_failure_t; + +//////////////////////////////////////////////////////////////////////////////// +//// Function types +//////////////////////////////////////////////////////////////////////////////// + +/** Constructor/destructor. Used for initializing / deinitializing components. */ +typedef void (*ecs_xtor_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx); + +/** Copy is invoked when a component is copied into another component. */ +typedef void (*ecs_copy_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + const void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Move is invoked when a component is moved to another component. */ +typedef void (*ecs_move_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx); + + +#ifdef __cplusplus +} +#endif + +#endif +#ifndef FLECS_API_SUPPORT_H +#define FLECS_API_SUPPORT_H + + +/** Supporting types and functions that need to be exposed either in support of + * the public API or for unit tests, but that may change between minor / patch + * releases. */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** This reserves entity ids for components. Regular entity ids will start after + * this constant. This affects performance of table traversal, as edges with ids + * lower than this constant are looked up in an array, whereas constants higher + * than this id are looked up in a map. Increasing this value can improve + * performance at the cost of (significantly) higher memory usage. */ +#define ECS_HI_COMPONENT_ID (256) /* Maximum number of components */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Global type handles +//////////////////////////////////////////////////////////////////////////////// + +/** Type handles to builtin components */ +FLECS_API +extern ecs_type_t + ecs_type(EcsComponent), + ecs_type(EcsComponentLifecycle), + ecs_type(EcsType), + ecs_type(EcsName); + +/** This allows passing 0 as type to functions that accept types */ +#define FLECS__TNULL 0 +#define FLECS__T0 0 + + +//////////////////////////////////////////////////////////////////////////////// +//// Functions used in declarative (macro) API +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +ecs_entity_t ecs_new_entity( + ecs_world_t *world, + ecs_entity_t e, + const char *id, + const char *components); + +FLECS_API +ecs_entity_t ecs_new_component( + ecs_world_t *world, + ecs_entity_t e, + const char *id, + size_t size, + size_t alignment); + +FLECS_API +ecs_entity_t ecs_new_module( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + size_t size, + size_t alignment); + +FLECS_API +ecs_entity_t ecs_new_type( + ecs_world_t *world, + ecs_entity_t e, + const char *id, + const char *components); + +FLECS_API +ecs_entity_t ecs_new_prefab( + ecs_world_t *world, + ecs_entity_t e, + const char *id, + const char *sig); + +FLECS_API +ecs_entity_t ecs_new_system( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + ecs_entity_t phase, + const char *signature, + ecs_iter_action_t action); + +FLECS_API +ecs_entity_t ecs_new_trigger( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + ecs_entity_t kind, + const char *component, + ecs_iter_action_t action); + +FLECS_API +ecs_entity_t ecs_new_pipeline( + ecs_world_t *world, + ecs_entity_t e, + const char *name, + const char *expr); + +FLECS_API +char* ecs_module_path_from_c( + const char *c_name); + +FLECS_API +bool ecs_component_has_actions( + ecs_world_t *world, + ecs_entity_t component); + +//////////////////////////////////////////////////////////////////////////////// +//// Signature API +//////////////////////////////////////////////////////////////////////////////// + +typedef enum ecs_sig_inout_kind_t { + EcsInOut, + EcsIn, + EcsOut +} ecs_sig_inout_kind_t; + +/** Type that is used by systems to indicate where to fetch a component from */ +typedef enum ecs_sig_from_kind_t { + EcsFromAny, /* Get component from self (default) */ + EcsFromOwned, /* Get owned component from self */ + EcsFromShared, /* Get shared component from self */ + EcsFromParent, /* Get component from container */ + EcsFromSystem, /* Get component from system */ + EcsFromEmpty, /* Get entity handle by id */ + EcsFromEntity, /* Get component from other entity */ + EcsCascade /* Walk component in cascading (hierarchy) order */ +} ecs_sig_from_kind_t; + +/** Type describing an operator used in an signature of a system signature */ +typedef enum ecs_sig_oper_kind_t { + EcsOperAnd = 0, + EcsOperOr = 1, + EcsOperNot = 2, + EcsOperOptional = 3, + EcsOperAll = 4, + EcsOperLast = 5 +} ecs_sig_oper_kind_t; + +/** Type that describes a single column in the system signature */ +typedef struct ecs_sig_column_t { + ecs_sig_from_kind_t from_kind; /* Element kind (Entity, Component) */ + ecs_sig_oper_kind_t oper_kind; /* Operator kind (AND, OR, NOT) */ + ecs_sig_inout_kind_t inout_kind; /* Is component read or written */ + union { + ecs_vector_t *type; /* Used for OR operator */ + ecs_entity_t component; /* Used for AND operator */ + } is; + ecs_entity_t source; /* Source entity (used with FromEntity) */ + char *name; /* Name of column */ +} ecs_sig_column_t; + +/** Type that stores a parsed signature */ +typedef struct ecs_sig_t { + const char *name; /* Optional name used for debugging */ + char *expr; /* Original expression string */ + ecs_vector_t *columns; /* Columns that contain parsed data */ +} ecs_sig_t; + +/** Parse signature. */ +FLECS_API +void ecs_sig_init( + ecs_world_t *world, + const char *name, + const char *expr, + ecs_sig_t *sig); + +/** Release signature resources */ +FLECS_API +void ecs_sig_deinit( + ecs_sig_t *sig); + +/** Add column to signature. */ +FLECS_API +int ecs_sig_add( + ecs_world_t *world, + ecs_sig_t *sig, + ecs_sig_from_kind_t from_kind, + ecs_sig_oper_kind_t oper_kind, + ecs_sig_inout_kind_t access_kind, + ecs_entity_t component, + ecs_entity_t source, + const char *arg_name); + +/** Create query based on signature object. */ +FLECS_API +ecs_query_t* ecs_query_new_w_sig( + ecs_world_t *world, + ecs_entity_t system, + ecs_sig_t *sig); + +/** Get signature object from query */ +FLECS_API +ecs_sig_t* ecs_query_get_sig( + ecs_query_t *query); + + +#define ECS_INVALID_ENTITY (1) +#define ECS_INVALID_PARAMETER (2) +#define ECS_INVALID_COMPONENT_ID (3) +#define ECS_INVALID_EXPRESSION (4) +#define ECS_INVALID_TYPE_EXPRESSION (5) +#define ECS_INVALID_SIGNATURE (6) +#define ECS_UNKNOWN_COMPONENT_ID (7) +#define ECS_UNKNOWN_TYPE_ID (8) +#define ECS_TYPE_NOT_AN_ENTITY (9) +#define ECS_MISSING_SYSTEM_CONTEXT (10) +#define ECS_NOT_A_COMPONENT (11) +#define ECS_INTERNAL_ERROR (12) +#define ECS_MORE_THAN_ONE_PREFAB (13) +#define ECS_ALREADY_DEFINED (14) +#define ECS_INVALID_COMPONENT_SIZE (15) +#define ECS_INVALID_COMPONENT_ALIGNMENT (16) +#define ECS_OUT_OF_MEMORY (17) +#define ECS_MODULE_UNDEFINED (18) +#define ECS_COLUMN_INDEX_OUT_OF_RANGE (19) +#define ECS_COLUMN_IS_NOT_SHARED (20) +#define ECS_COLUMN_IS_SHARED (21) +#define ECS_COLUMN_HAS_NO_DATA (22) +#define ECS_COLUMN_TYPE_MISMATCH (23) +#define ECS_INVALID_WHILE_MERGING (24) +#define ECS_INVALID_WHILE_ITERATING (25) +#define ECS_INVALID_FROM_WORKER (26) +#define ECS_UNRESOLVED_IDENTIFIER (27) +#define ECS_OUT_OF_RANGE (28) +#define ECS_COLUMN_IS_NOT_SET (29) +#define ECS_UNRESOLVED_REFERENCE (30) +#define ECS_THREAD_ERROR (31) +#define ECS_MISSING_OS_API (32) +#define ECS_TYPE_TOO_LARGE (33) +#define ECS_INVALID_PREFAB_CHILD_TYPE (34) +#define ECS_UNSUPPORTED (35) +#define ECS_NO_OUT_COLUMNS (36) +#define ECS_COLUMN_ACCESS_VIOLATION (37) +#define ECS_DESERIALIZE_COMPONENT_ID_CONFLICT (38) +#define ECS_DESERIALIZE_COMPONENT_SIZE_CONFLICT (39) +#define ECS_DESERIALIZE_FORMAT_ERROR (40) +#define ECS_INVALID_REACTIVE_SIGNATURE (41) +#define ECS_INCONSISTENT_COMPONENT_NAME (42) +#define ECS_TYPE_CONSTRAINT_VIOLATION (43) +#define ECS_COMPONENT_NOT_REGISTERED (44) +#define ECS_INCONSISTENT_COMPONENT_ID (45) +#define ECS_INVALID_CASE (46) +#define ECS_COMPONENT_NAME_IN_USE (47) +#define ECS_INCONSISTENT_NAME (48) +#define ECS_INCONSISTENT_COMPONENT_ACTION (49) +#define ECS_INVALID_OPERATION (50) + +/** Calculate offset from address */ +#define ECS_OFFSET(o, offset) (void*)(((uintptr_t)(o)) + ((uintptr_t)(offset))) + +#ifdef __cplusplus +} +#endif + +#endif +/** Internal utility functions for tracing, warnings and errors. */ + +#ifndef FLECS_LOG_H +#define FLECS_LOG_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Color macro's +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_BLACK "\033[1;30m" +#define ECS_RED "\033[0;31m" +#define ECS_GREEN "\033[0;32m" +#define ECS_YELLOW "\033[0;33m" +#define ECS_BLUE "\033[0;34m" +#define ECS_MAGENTA "\033[0;35m" +#define ECS_CYAN "\033[0;36m" +#define ECS_WHITE "\033[1;37m" +#define ECS_GREY "\033[0;37m" +#define ECS_NORMAL "\033[0;49m" +#define ECS_BOLD "\033[1;49m" + + +//////////////////////////////////////////////////////////////////////////////// +//// Tracing +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void _ecs_trace( + int level, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_warn( + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_err( + const char *file, + int32_t line, + const char *fmt, + ...); +FLECS_API +void ecs_log_push(void); + +FLECS_API +void ecs_log_pop(void); + +#ifndef FLECS_LEGACY + +#define ecs_trace(lvl, ...)\ + _ecs_trace(lvl, __FILE__, __LINE__, __VA_ARGS__) + +#define ecs_warn(...)\ + _ecs_warn(__FILE__, __LINE__, __VA_ARGS__) + +#define ecs_err(...)\ + _ecs_err(__FILE__, __LINE__, __VA_ARGS__) + + +/* If in debug mode and no tracing verbosity is defined, compile all tracing */ +#if !defined(NDEBUG) && !(defined(ECS_TRACE_0) || defined(ECS_TRACE_1) || defined(ECS_TRACE_2) || defined(ECS_TRACE_3)) +#define ECS_TRACE_3 +#endif + +#ifndef NDEBUG +#if defined(ECS_TRACE_3) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) ecs_trace(2, __VA_ARGS__); +#define ecs_trace_3(...) ecs_trace(3, __VA_ARGS__); + +#elif defined(ECS_TRACE_2) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) ecs_trace(2, __VA_ARGS__); +#define ecs_trace_3(...) + +#elif defined(ECS_TRACE_1) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) +#define ecs_trace_3(...) +#endif +#else +#define ecs_trace_1(...) +#define ecs_trace_2(...) +#define ecs_trace_3(...) +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Exceptions +//////////////////////////////////////////////////////////////////////////////// + +/** Get description for error code */ +FLECS_API +const char* ecs_strerror( + int32_t error_code); + +/** Abort */ +FLECS_API +void _ecs_abort( + int32_t error_code, + const char *param, + const char *file, + int32_t line); + +#define ecs_abort(error_code, param)\ + _ecs_abort(error_code, param, __FILE__, __LINE__); abort() + +/** Assert */ +FLECS_API +void _ecs_assert( + bool condition, + int32_t error_code, + const char *param, + const char *condition_str, + const char *file, + int32_t line); + +#ifdef NDEBUG +#define ecs_assert(condition, error_code, param) +#else +#define ecs_assert(condition, error_code, param)\ + _ecs_assert(condition, error_code, param, #condition, __FILE__, __LINE__);\ + assert(condition) +#endif + +FLECS_API +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...); + +#ifndef FLECS_LEGACY + +#define ecs_parser_error(name, expr, column, ...)\ + _ecs_parser_error(name, expr, column, __VA_ARGS__);\ + abort() + +#endif + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @file type.h + * @brief Type API. + */ + +#ifndef FLECS_TYPE_H +#define FLECS_TYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API +ecs_type_t ecs_type_from_entity( + ecs_world_t *world, + ecs_entity_t entity); + +FLECS_API +ecs_entity_t ecs_type_to_entity( + ecs_world_t *world, + ecs_type_t type); + +FLECS_API +char* ecs_type_str( + ecs_world_t *world, + ecs_type_t type); + +FLECS_API +ecs_type_t ecs_type_from_str( + ecs_world_t *world, + const char *expr); + +FLECS_API +ecs_type_t ecs_type_find( + ecs_world_t *world, + ecs_entity_t *array, + int32_t count); + +FLECS_API +ecs_type_t ecs_type_merge( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t type_add, + ecs_type_t type_remove); + +FLECS_API +ecs_type_t ecs_type_add( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity); + +FLECS_API +ecs_type_t ecs_type_remove( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity); + +FLECS_API +bool ecs_type_has_entity( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity); + +FLECS_API +bool ecs_type_has_type( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t has); + +FLECS_API +bool ecs_type_owns_entity( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity, + bool owned); + +FLECS_API +bool ecs_type_owns_type( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t has, + bool owned); + +FLECS_API +ecs_entity_t ecs_type_get_entity_for_xor( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t xor_tag); + +FLECS_API +int32_t ecs_type_index_of( + ecs_type_t type, + ecs_entity_t component); + +FLECS_API +int32_t ecs_type_trait_index_of( + ecs_type_t type, + int32_t start_index, + ecs_entity_t trait); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + + +/** + * @defgroup builtin_types Builtin Types + * @{ + */ + +/** Entity name. */ +typedef struct EcsName { + const char *value; /**< Entity name */ + const char *symbol; /**< Optional symbol name, if it differs from name */ + char *alloc_value; /**< If set, value will be freed on destruction */ +} EcsName; + +/** Component information. */ +typedef struct EcsComponent { + ecs_size_t size; /**< Component size */ + ecs_size_t alignment; /**< Component alignment */ +} EcsComponent; + +/** Component that stores an ecs_type_t. + * This component allows for the creation of entities that represent a type, and + * therefore the creation of named types. This component is typically + * instantiated by ECS_TYPE. */ +typedef struct EcsType { + ecs_type_t type; /**< Preserved nested types */ + ecs_type_t normalized; /**< Union of type and nested AND types */ +} EcsType; + +/** Component that contains lifecycle callbacks for a component. */ +typedef struct EcsComponentLifecycle { + ecs_xtor_t ctor; /**< Component constructor */ + ecs_xtor_t dtor; /**< Component destructor */ + ecs_copy_t copy; /**< Component copy */ + ecs_move_t move; /**< Component move */ + void *ctx; /**< User defined context */ +} EcsComponentLifecycle; + +/** Component used for registering component triggers */ +typedef struct EcsTrigger { + ecs_entity_t kind; + ecs_iter_action_t action; + ecs_entity_t component; + ecs_entity_t self; + void *ctx; +} EcsTrigger; + +/** @} */ + + +/** + * @defgroup type_roles Type Roles + * @{ + */ + +/* Type roles are used to indicate the role of an entity in a type. If no flag + * is specified, the entity is interpreted as a regular component or tag. Flags + * are added to an entity by using a bitwise OR (|). An example: + * + * ecs_entity_t parent = ecs_new(world, 0); + * ecs_entity_t child = ecs_add_entity(world, e, ECS_CHILDOF | parent); + * + * Type flags can also be used in type expressions, without the ECS prefix: + * + * ECS_ENTITY(world, Base, Position); + * ECS_TYPE(world, InstanceOfBase, INSTANCEOF | Base); + */ + +/** Role bit added to roles to differentiate between roles and generations */ +#define ECS_ROLE ((uint64_t)1 << 63) + +/** The INSTANCEOF role indicates that the components from the entity should be + * shared with the entity that instantiates the type. */ +#define ECS_INSTANCEOF (ECS_ROLE | ((ecs_entity_t)0x7E << 56)) + +/** The CHILDOF role indicates that the entity should be treated as a parent of + * the entity that instantiates the type. */ +#define ECS_CHILDOF (ECS_ROLE | ((ecs_entity_t)0x7D << 56)) + +/** Cases are used to switch between mutually exclusive components */ +#define ECS_CASE (ECS_ROLE | ((ecs_entity_t)0x7C << 56)) + +/** Switches allow for fast switching between mutually exclusive components */ +#define ECS_SWITCH (ECS_ROLE | ((ecs_entity_t)0x7B << 56)) + +/** The TRAIT role indicates that the entity is a trait identifier. */ +#define ECS_TRAIT (ECS_ROLE | ((ecs_entity_t)0x7A << 56)) + +/** Enforce that all entities of a type are present in the type. + * This flag can only be used in combination with an entity that has EcsType. */ +#define ECS_AND (ECS_ROLE | ((ecs_entity_t)0x79 << 56)) + +/** Enforce that at least one entity of a type must be present in the type. + * This flag can only be used in combination with an entity that has EcsType. */ +#define ECS_OR (ECS_ROLE | ((ecs_entity_t)0x78 << 56)) + +/** Enforce that exactly one entity of a type must be present in the type. + * This flag can only be used in combination with an entity that has EcsType. + * When another entity of the XOR'd type is added to an entity of this type, the + * previous entity is removed from the entity. This makes XOR useful for + * implementing state machines, as it allows for traversing states while + * ensuring that only one state is ever active at the same time. */ +#define ECS_XOR (ECS_ROLE | ((ecs_entity_t)0x77 << 56)) + +/** None of the entities in a type may be added to the type. + * This flag can only be used in combination with an entity that has EcsType. */ +#define ECS_NOT (ECS_ROLE | ((ecs_entity_t)0x76 << 56)) + +/** Enforce ownership of a component */ +#define ECS_OWNED (ECS_ROLE | ((ecs_entity_t)0x75 << 56)) + +/** Track whether component is enabled or not */ +#define ECS_DISABLED (ECS_ROLE | ((ecs_entity_t)0x74 << 56)) + +/** @} */ + +/** + * @defgroup builtin_tags Builtin Tags + * @{ + */ + +/* Builtin tag ids */ +#define EcsModule (ECS_HI_COMPONENT_ID + 0) +#define EcsPrefab (ECS_HI_COMPONENT_ID + 1) +#define EcsHidden (ECS_HI_COMPONENT_ID + 2) +#define EcsDisabled (ECS_HI_COMPONENT_ID + 3) +#define EcsDisabledIntern (ECS_HI_COMPONENT_ID + 4) +#define EcsInactive (ECS_HI_COMPONENT_ID + 5) +#define EcsOnDemand (ECS_HI_COMPONENT_ID + 6) +#define EcsMonitor (ECS_HI_COMPONENT_ID + 7) +#define EcsPipeline (ECS_HI_COMPONENT_ID + 8) + +/* Trigger tags */ +#define EcsOnAdd (ECS_HI_COMPONENT_ID + 9) +#define EcsOnRemove (ECS_HI_COMPONENT_ID + 10) + +/* Set system tags */ +#define EcsOnSet (ECS_HI_COMPONENT_ID + 11) +#define EcsUnSet (ECS_HI_COMPONENT_ID + 12) + +/* Builtin pipeline tags */ +#define EcsPreFrame (ECS_HI_COMPONENT_ID + 13) +#define EcsOnLoad (ECS_HI_COMPONENT_ID + 14) +#define EcsPostLoad (ECS_HI_COMPONENT_ID + 15) +#define EcsPreUpdate (ECS_HI_COMPONENT_ID + 16) +#define EcsOnUpdate (ECS_HI_COMPONENT_ID + 17) +#define EcsOnValidate (ECS_HI_COMPONENT_ID + 18) +#define EcsPostUpdate (ECS_HI_COMPONENT_ID + 19) +#define EcsPreStore (ECS_HI_COMPONENT_ID + 20) +#define EcsOnStore (ECS_HI_COMPONENT_ID + 21) +#define EcsPostFrame (ECS_HI_COMPONENT_ID + 22) + +/** Builtin entity ids */ +#define EcsFlecs (ECS_HI_COMPONENT_ID + 23) +#define EcsFlecsCore (ECS_HI_COMPONENT_ID + 24) +#define EcsWorld (ECS_HI_COMPONENT_ID + 25) +#define EcsSingleton (ECS_HI_COMPONENT_ID + 26) +#define EcsWildcard (ECS_HI_COMPONENT_ID + 27) + +/* Value used to quickly check if component is builtin. This is used to quickly + * filter out tables with builtin components (for example for ecs_delete) */ +#define EcsLastInternalComponentId (ecs_typeid(EcsSystem)) + +/* The first user-defined component starts from this id. Ids up to this number + * are reserved for builtin components */ +#define EcsFirstUserComponentId (32) + +/* The first user-defined entity starts from this id. Ids up to this number + * are reserved for builtin components */ +#define EcsFirstUserEntityId (ECS_HI_COMPONENT_ID + 32) + +/** @} */ + +/** + * @defgroup convenience_macros Convenience Macro's + * @{ + */ + +/* Macro's rely on variadic arguments which are C99 and above */ +#ifndef FLECS_LEGACY + +/** Declare an extern component variable. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_ENTITY_DECLARE. + * + * Example: + * ECS_COMPONENT_EXTERN(Position); + */ +#define ECS_ENTITY_EXTERN(id)\ + extern ecs_entity_t id + +/** Declare an entity variable outside the scope of a function. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_ENTITY_DEFINE. + * + * Example: + * ECS_ENTITY_DECLARE(Position); + */ +#define ECS_ENTITY_DECLARE(id)\ + ecs_entity_t id + +/** Define a component, store in variable outside of the current scope. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_ENTITY_DECLARE. + * + * Example: + * ECS_ENTITY_DEFINE(world, Position); + */ +#define ECS_ENTITY_DEFINE(world, id, ...)\ + id = ecs_new_entity(world, id, #id, #__VA_ARGS__) + +/** Declare a named entity with a type expression. + * Example: + * ECS_ENTITY(world, MyEntity, Position, Velocity); + */ +#define ECS_ENTITY(world, id, ...)\ + ecs_entity_t id = ecs_new_entity(world, 0, #id, #__VA_ARGS__);\ + (void)id + +/** Declare a prefab with a type expression. + * Example: + * ECS_PREFAB(world, MyEntity, Position, Velocity); + */ +#define ECS_PREFAB(world, id, ...) \ + ecs_entity_t id = ecs_new_prefab(world, 0, #id, #__VA_ARGS__);\ + (void)id + +/** Declare a component. + * Example: + * ECS_COMPONENT(world, Position); + */ +#define ECS_COMPONENT(world, id) \ + ECS_ENTITY_VAR(id) = ecs_new_component(world, 0, #id, sizeof(id), ECS_ALIGNOF(id));\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + (void)ecs_typeid(id);\ + (void)ecs_type(id) + +/** Declare an extern component variable. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DECLARE. + * + * Example: + * ECS_COMPONENT_EXTERN(Position); + */ +#define ECS_COMPONENT_EXTERN(id)\ + extern ECS_ENTITY_VAR(id);\ + extern ecs_type_t ecs_type(id) + +/** Declare a component variable outside the scope of a function. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DEFINE. + * + * Example: + * ECS_COMPONENT_IMPL(Position); + */ +#define ECS_COMPONENT_DECLARE(id)\ + ECS_ENTITY_VAR(id);\ + ecs_type_t ecs_type(id) + +/** Define a component, store in variable outside of the current scope. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DECLARE. + * + * Example: + * ECS_COMPONENT_DEFINE(world, Position); + */ +#define ECS_COMPONENT_DEFINE(world, id)\ + ecs_typeid(id) = ecs_new_component(world, ecs_typeid(id), #id, sizeof(id), ECS_ALIGNOF(id));\ + ecs_type(id) = ecs_type_from_entity(world, ecs_typeid(id)) + +/** Declare a tag. + * Example: + * ECS_TAG(world, MyTag); + */ +#define ECS_TAG(world, id)\ + ECS_ENTITY(world, id, 0);\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &id, 1);\ + (void)ecs_type(id) + +/** Declare an extern tag variable. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DECLARE. + * + * Example: + * ECS_TAG_EXTERN(Enemy); + */ +#define ECS_TAG_EXTERN(id)\ + extern ecs_entity_t id;\ + extern ecs_type_t ecs_type(id) + +/** Declare a tag variable outside the scope of a function. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DEFINE. + * + * Example: + * ECS_TAG_DECLARE(Enemy); + */ +#define ECS_TAG_DECLARE(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +/** Define a tag, store in variable outside of the current scope. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DECLARE. + * + * Example: + * ECS_TAG_DEFINE(world, Enemy); + */ +#define ECS_TAG_DEFINE(world, id)\ + id = ecs_new_entity(world, id, #id, 0);\ + ecs_type(id) = ecs_type_from_entity(world, id) + +/** Declare a type. + * Example: + * ECS_TYPE(world, MyType, Position, Velocity); + */ +#define ECS_TYPE(world, id, ...) \ + ecs_entity_t id = ecs_new_type(world, 0, #id, #__VA_ARGS__);\ + ECS_TYPE_VAR(id) = ecs_type_from_entity(world, id);\ + (void)id;\ + (void)ecs_type(id) + +/** Declare an extern type variable. + * Use this macro in a header when defining a type globally. + * Must be used together with ECS_TYPE_DECLARE. + * + * Example: + * ECS_TYPE_EXTERN(Movable); + */ +#define ECS_TYPE_EXTERN(id)\ + extern ecs_entity_t id;\ + extern ecs_type_t ecs_type(id) + +/** Declare a type variable outside the scope of a function. + * Use this macro in a header when defining a type globally. + * Must be used together with ECS_TYPE_DEFINE. + * + * Example: + * ECS_TYPE_DECLARE(Movable); + */ +#define ECS_TYPE_DECLARE(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +/** Define a type, store in variable outside of the current scope. + * Use this macro in a header when defining a type globally. + * Must be used together with ECS_TYPE_DECLARE. + * + * Example: + * ECS_TYPE_DEFINE(world, Movable, Position, Velocity); + */ +#define ECS_TYPE_DEFINE(world, id, ...)\ + id = ecs_new_type(world, 0, #id, #__VA_ARGS__);\ + ecs_type(id) = ecs_type_from_entity(world, id);\ + +/** Declare a constructor. + * Example: + * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); + */ +#define ECS_CTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) + +/** Declare a destructor. + * Example: + * ECS_DTOR(MyType, ptr, { free(ptr->value); }); + */ +#define ECS_DTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, dtor, var, __VA_ARGS__) + +/** Declare a copy action. + * Example: + * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); + */ +#define ECS_COPY(type, dst_var, src_var, ...)\ + ECS_COPY_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare a move action. + * Example: + * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); + */ +#define ECS_MOVE(type, dst_var, src_var, ...)\ + ECS_MOVE_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/* Map from typename to function name of component lifecycle action */ +#define ecs_ctor(type) type##_ctor +#define ecs_dtor(type) type##_dtor +#define ecs_copy(type) type##_copy +#define ecs_move(type) type##_move + +#endif /* FLECS_LEGACY */ + +/** @} */ + +/** + * @defgroup world_api World API + * @{ + */ + +/** Create a new world. + * A world manages all the ECS data and supporting infrastructure. Applications + * must have at least one world. Entities, component and system handles are + * local to a world and should not be shared between worlds. + * + * This operation creates a world with all builtin modules loaded. + * + * @return A new world object + */ +FLECS_API +ecs_world_t* ecs_init(void); + +/** Same as ecs_init, but with minimal set of modules loaded. + * + * @return A new world object + */ +FLECS_API +ecs_world_t* ecs_mini(void); + +/** Create a new world with arguments. + * Same as ecs_init, but allows passing in command line arguments. These can be + * used to dynamically enable flecs features to an application. Currently these + * arguments are not used. + * + * @return A new world object + */ +FLECS_API +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]); + +/** Delete a world. + * This operation deletes the world, and everything it contains. + * + * @param world The world to delete. + * @return Zero if successful, non-zero if failed. + */ +FLECS_API +int ecs_fini( + ecs_world_t *world); + +/** Register action to be executed when world is destroyed. + * Fini actions are typically used when a module needs to clean up before a + * world shuts down. + * + * @param world The world. + * @param action The function to execute. + * @param ctx Userdata to pass to the function */ +FLECS_API +void ecs_atfini( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx); + +/** Register action to be executed once after frame. + * Post frame actions are typically used for calling operations that cannot be + * invoked during iteration, such as changing the number of threads. + * + * @param world The world. + * @param action The function to execute. + * @param ctx Userdata to pass to the function */ +FLECS_API +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx); + +/** Register ctor, dtor, copy & move actions for component. + * + * @param world The world. + * @param component The component id for which to register the actions + * @param actions Type that contains the component actions. + */ +FLECS_API +void ecs_set_component_actions_w_entity( + ecs_world_t *world, + ecs_entity_t component, + EcsComponentLifecycle *actions); + +#ifndef FLECS_LEGACY +#define ecs_set_component_actions(world, component, ...)\ + ecs_set_component_actions_w_entity(world, ecs_typeid(component), &(EcsComponentLifecycle)__VA_ARGS__) + +#endif +/** Set a world context. + * This operation allows an application to register custom data with a world + * that can be accessed anywhere where the application has the world object. + * + * @param world The world. + * @param ctx A pointer to a user defined structure. + */ +FLECS_API +void ecs_set_context( + ecs_world_t *world, + void *ctx); + +/** Get the world context. + * This operation retrieves a previously set world context. + * + * @param world The world. + * @return The context set with ecs_set_context. If no context was set, the + * function returns NULL. + */ +FLECS_API +void* ecs_get_context( + ecs_world_t *world); + +/** Get world info. + * + * @param world The world. + * @return Pointer to the world info. This pointer will remain valid for as long + * as the world is valid. + */ +FLECS_API +const ecs_world_info_t* ecs_get_world_info( + ecs_world_t *world); + +/** Dimension the world for a specified number of entities. + * This operation will preallocate memory in the world for the specified number + * of entities. Specifying a number lower than the current number of entities in + * the world will have no effect. Note that this function does not allocate + * memory for components (use ecs_dim_type for that). + * + * @param world The world. + * @param entity_count The number of entities to preallocate. + */ +FLECS_API +void ecs_dim( + ecs_world_t *world, + int32_t entity_count); + +/** Dimension a type for a specified number of entities. + * This operation will preallocate memory for a type (table) for the + * specified number of entities. Specifying a number lower than the current + * number of entities in the table will have no effect. + * + * @param world The world. + * @param type Handle to the type, as obtained by ecs_type_get. + * @param entity_count The number of entities to preallocate. + */ +FLECS_API +void ecs_dim_type( + ecs_world_t *world, + ecs_type_t type, + int32_t entity_count); + +/** Set a range for issueing new entity ids. + * This function constrains the entity identifiers returned by ecs_new to the + * specified range. This operation can be used to ensure that multiple processes + * can run in the same simulation without requiring a central service that + * coordinates issueing identifiers. + * + * If id_end is set to 0, the range is infinite. If id_end is set to a non-zero + * value, it has to be larger than id_start. If id_end is set and ecs_new is + * invoked after an id is issued that is equal to id_end, the application will + * abort. + * + * @param world The world. + * @param id_start The start of the range. + * @param id_end The end of the range. + */ +FLECS_API +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end); + +/** Enable/disable range limits. + * When an application is both a receiver of range-limited entities and a + * producer of range-limited entities, range checking needs to be temporarily + * disabled when inserting received entities. Range checking is disabled on a + * stage, so setting this value is thread safe. + * + * @param world The world. + * @param enable True if range checking should be enabled, false to disable. + * @return The previous value. + */ +FLECS_API +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable); + +/** Enable world locking while in progress. + * When locking is enabled, Flecs will lock the world while in progress. This + * allows applications to interact with the world from other threads without + * running into race conditions. + * + * This is a better alternative to applications putting a lock around calls to + * ecs_progress, since ecs_progress can sleep when FPS control is enabled, + * which is time during which other threads could perform work. + * + * Locking must be enabled before applications can use the ecs_lock and + * ecs_unlock functions. Locking is turned off by default. + * + * @param world The world. + * @param enable True if locking is to be enabled. + * @result The previous value of the setting. + */ +FLECS_API +bool ecs_enable_locking( + ecs_world_t *world, + bool enable); + +/** Locks the world. + * See ecs_enable_locking for details. + * + * @param world The world. + */ +FLECS_API +void ecs_lock( + ecs_world_t *world); + +/** Unlocks the world. + * See ecs_enable_locking for details. + * + * @param world The world. + */ +FLECS_API +void ecs_unlock( + ecs_world_t *world); + +/** Wait until world becomes available. + * When a non-flecs thread needs to interact with the world, it should invoke + * this function to wait until the world becomes available (as in, it is not + * progressing the frame). Invoking this function guarantees that the thread + * will not starve. (as opposed to simply taking the world lock). + * + * An application will have to invoke ecs_end_wait after this function returns. + * + * @param world The world. + */ +FLECS_API +void ecs_begin_wait( + ecs_world_t *world); + +/** Release world after calling ecs_begin_wait. + * This operation should be invoked after invoking ecs_begin_wait, and will + * release the world back to the thread running the main loop. + * + * @param world The world. + */ +FLECS_API +void ecs_end_wait( + ecs_world_t *world); + +/** Enable or disable tracing. + * This will enable builtin tracing. For tracing to work, it will have to be + * compiled in which requires defining one of the following macro's: + * + * ECS_TRACE_0 - All tracing is disabled + * ECS_TRACE_1 - Enable tracing level 1 + * ECS_TRACE_2 - Enable tracing level 2 and below + * ECS_TRACE_3 - Enable tracing level 3 and below + * + * If no tracing level is defined and this is a debug build, ECS_TRACE_3 will + * have been automatically defined. + * + * The provided level corresponds with the tracing level. If -1 is provided as + * value, warnings are disabled. If -2 is provided, errors are disabled as well. + * + * @param level Desired tracing level. + */ +FLECS_API +void ecs_tracing_enable( + int level); + +/** @} */ + +/** + * @defgroup creating_entities Creating Entities + * @{ + */ + +/** Create new entity id. + * This operation returns an unused entity id. + * + * @param world The world. + * @return The new entity id. + */ +FLECS_API +ecs_entity_t ecs_new_id( + ecs_world_t *world); + +/** Create new component id. + * This operation returns a new component id. Component ids are the same as + * entity ids, but can make use of the [1 .. ECS_HI_COMPONENT_ID] range. + * + * This operation does not recycle ids. + * + * @param world The world. + * @return The new component id. + */ +FLECS_API +ecs_entity_t ecs_new_component_id( + ecs_world_t *world); + +/** Create new entity. + * This operation creates a new entity with a single entity in its type. The + * entity may contain type roles. This operation recycles ids. + * + * @param world The world. + * @param entity The entity to initialize the new entity with. + * @return The new entity. + */ +FLECS_API +ecs_entity_t ecs_new_w_entity( + ecs_world_t *world, + ecs_entity_t entity); + +/** Create new entity. + * This operation creates a new entity initialized with a type. This operation + * recycles ids. + * + * @param world The world. + * @param type The type to initialize the new entity with. + * @return The new entity. + */ +FLECS_API +ecs_entity_t ecs_new_w_type( + ecs_world_t *world, + ecs_type_t type); + +/** Create a new entity. + * This operation creates a new entity with a single component in its type. This + * operation accepts variables created with ECS_COMPONENT, ECS_TYPE and ECS_TAG. + * This operation recycles ids. + * + * @param world The world. + * @param type The component type. + * @return The new entity. + */ +#define ecs_new(world, type)\ + ecs_new_w_type(world, ecs_type(type)) + +/** Create N new entities. + * This operation is the same as ecs_new_w_entity, but creates N entities + * instead of one and does not recycle ids. + * + * @param world The world. + * @param entity The entity. + * @param count The number of entities to create. + * @return The first entity id of the newly created entities. + */ +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + int32_t count); + +/** Create N new entities. + * This operation is the same as ecs_new_w_type, but creates N entities + * instead of one and does not recycle ids. + * + * @param world The world. + * @param type The type. + * @param count The number of entities to create. + * @return The first entity id of the newly created entities. + */ +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_type( + ecs_world_t *world, + ecs_type_t type, + int32_t count); + +/** Create N new entities and initialize components. + * This operation is the same as ecs_bulk_new_w_type, but initializes components + * with the provided component array. Instead of a type the operation accepts an + * array of component identifiers (entities). The component arrays need to be + * provided in the same order as the component identifiers. + * + * @param world The world. + * @param components Array with component identifiers. + * @param count The number of entities to create. + * @param data The data arrays to initialize the components with. + * @return The first entity id of the newly created entities. + */ +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_data( + ecs_world_t *world, + int32_t count, + ecs_entities_t *component_ids, + void *data); + +/** Create N new entities. + * This operation is the same as ecs_new, but creates N entities + * instead of one and does not recycle ids. + * + * @param world The world. + * @param component The component type. + * @param count The number of entities to create. + * @return The first entity id of the newly created entities. + */ +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_type(world, ecs_type(component), count) + +/** Clone an entity + * This operation clones the components of one entity into another entity. If + * no destination entity is provided, a new entity will be created. Component + * values are not copied unless copy_value is true. + * + * @param world The world. + * @param dst The entity to copy the components to. + * @param src The entity to copy the components from. + * @param copy_value If true, the value of components will be copied to dst. + * @return The destination entity. + */ +FLECS_API +ecs_entity_t ecs_clone( + ecs_world_t *world, + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value); + +/** @} */ + +/** + * @defgroup adding_removing Adding & Removing + * @{ + */ + +/** Add an entity to an entity. + * This operation adds a single entity to the type of an entity. Type roles may + * be used in combination with the added entity. If the entity already has the + * entity, this operation will have no side effects. + * + * @param world The world. + * @param entity The entity. + * @param entity_add The entity to add. + */ +FLECS_API +void ecs_add_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t entity_add); + +/** Add a type to an entity. + * This operation adds a type to an entity. The resulting type of the entity + * will be the union of the previous type and the provided type. If the added + * type did not have new components, this operation will have no side effects. + * + * @param world The world. + * @param entity The entity. + * @param type The type to add. + */ +FLECS_API +void ecs_add_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +/** Add a component, type or tag to an entity. + * This operation adds a type to an entity. The resulting type of the entity + * will be the union of the previous type and the provided type. If the added + * type did not have new components, this operation will have no side effects. + * + * This operation accepts variables declared by ECS_COMPONENT, ECS_TYPE and + * ECS_TAG. + * + * @param world The world. + * @param entity The entity. + * @param component The component, type or tag to add. + */ +#define ecs_add(world, entity, component)\ + ecs_add_type(world, entity, ecs_type(component)) + + +/** Remove an entity from an entity. + * This operation removes a single entity from the type of an entity. Type roles + * may be used in combination with the added entity. If the entity does not have + * the entity, this operation will have no side effects. + * + * @param world The world. + * @param entity The entity. + * @param entity_remove The entity to remove. + */ +FLECS_API +void ecs_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t entity_remove); + +/** Remove a type from an entity. + * This operation removes a type to an entity. The resulting type of the entity + * will be the difference of the previous type and the provided type. If the + * type did not overlap with the entity type, this operation has no side effects. + * + * @param world The world. + * @param entity The entity. + * @param type The type to remove. + */ +FLECS_API +void ecs_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +/** Remove a component, type or tag from an entity. + * This operation removes a type to an entity. The resulting type of the entity + * will be the difference of the previous type and the provided type. If the + * type did not overlap with the entity type, this operation has no side effects. + * + * This operation accepts variables declared by ECS_COMPONENT, ECS_TYPE and + * ECS_TAG. + * + * @param world The world. + * @param entity The entity. + * @param component The component, type or tag to remove. + */ +#define ecs_remove(world, entity, type)\ + ecs_remove_type(world, entity, ecs_type(type)) + + +/** Add / remove entity from entities matching a filter. + * Combination of ecs_add_entity and ecs_remove_entity. + * + * @param world The world. + * @param entity The entity. + * @param to_add The entity to add. + * @param to_remove The entity to remove. + */ +FLECS_API +void ecs_add_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t to_add, + ecs_entity_t to_remove); + +/** Add / remove type from entities matching a filter. + * Combination of ecs_add_type and ecs_remove_type. + * + * @param world The world. + * @param entity The entity. + * @param to_add The type to add. + * @param to_remove The type to remove. + */ +FLECS_API +void ecs_add_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t to_add, + ecs_type_t to_remove); + +/** Add / remove component, type or tag from entity. + * Combination of ecs_add and ecs_remove. + * + * @param world The world. + * @param entity The entity. + * @param to_add The component, type or tag to add. + * @param to_remove The component, type or tag to remove. + */ +#define ecs_add_remove(world, entity, to_add, to_remove)\ + ecs_add_remove_type(world, entity, ecs_type(to_add), ecs_type(to_remove)) + +/** @} */ + + +/** + * @defgroup enabling_disabling Enabling & Disabling components. + * @{ + */ + +/** Enable or disable component. + * Enabling or disabling a component does not add or remove a component from an + * entity, but prevents it from being matched with queries. This operation can + * be useful when a component must be temporarily disabled without destroying + * its value. It is also a more performant operation for when an application + * needs to add/remove components at high frequency, as enabling/disabling is + * cheaper than a regular add or remove. + * + * @param world The world. + * @param entity The entity. + * @param component The component. + * @param enable True to enable the component, false to disable. + */ +FLECS_API void ecs_enable_component_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + bool enable); + +#define ecs_enable_component(world, entity, T, enable)\ + ecs_enable_component_w_entity(world, entity, ecs_typeid(T), enable) + +/** Test if component is enabled. + * Test whether a component is currently enabled or disabled. This operation + * will return true when the entity has the component and if it has not been + * disabled by ecs_enable_component. + * + * @param world The world. + * @param entity The entity. + * @param component The component. + * @return True if the component is enabled, otherwise false. + */ +FLECS_API bool ecs_is_component_enabled_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component); + +#define ecs_is_component_enabled(world, entity, T)\ + ecs_is_component_enabled_w_entity(world, entity, ecs_typeid(T)) + +/** @} */ + + +/** + * @defgroup traits Traits + * @{ + */ + +/** Add a trait + * This operation adds a trait from an entity. + * + * @param world The world. + * @param entity The entity. + * @param component The entity for which to remove the trait. + * @param trait The trait to remove. + */ +#define ecs_add_trait(world, entity, component, trait)\ + ecs_add_entity(world, entity, ecs_trait(component, trait)) + +/** Remove a trait + * This operation removes a trait from an entity. + * + * @param world The world. + * @param entity The entity. + * @param component The entity for which to remove the trait. + * @param trait The trait to remove. + */ +#define ecs_remove_trait(world, entity, component, trait)\ + ecs_remove_entity(world, entity, ecs_trait(component, trait)) + +/** Test if an entity has a trait. + * This operation returns true if the entity has the provided trait for the + * specified component in its type. + * + * @param world The world. + * @param entity The entity. + * @param component The entity. + * @param trait The entity. + * @return True if the entity has the trait, false if not. + */ +#define ecs_has_trait(world, entity, component, trait)\ + ecs_has_entity(world, entity, ecs_trait(component, trait)) + + +#ifndef FLECS_LEGACY +/** Set trait for component. + * This operation adds a trait for an entity and component. Traits can be added + * multiple times to the same entity, as long as it is for different components. + * + * Traits can be matched with systems by providing the TRAIT role to the + * trait component in the system signature. A system will match multiple times + * with the same entity if the trait is added for multiple components. + * + * * This operation can only be used with traits that are components. + * + * @param world The world. + * @param e The entity. + * @param component The component for which to add the trait. + * @param trait The trait to add. + */ +#define ecs_set_trait(world, entity, component, trait, ...)\ + ecs_set_ptr_w_entity(world, entity, ecs_trait(ecs_typeid(component), ecs_typeid(trait)), sizeof(trait), &(trait)__VA_ARGS__) + + +/** Set tag trait for component. + * This operation is similar to ecs_set_trait, but is used for trait tags. When + * a trait tag is set on an entity, the trait type is not used (tags have no + * type) and instead the component type is used. + * + * This operation can only be used with traits that are not components. + * + * @param world The world. + * @param e The entity. + * @param component The component for which to add the trait. + * @param trait The trait to add. + */ +#define ecs_set_trait_tag(world, entity, trait, component, ...)\ + ecs_set_ptr_w_entity(world, entity, ecs_trait(ecs_typeid(component), trait), sizeof(component), &(component)__VA_ARGS__) + +#endif + +/** Get trait for component. + * This operation obtains the value of a trait for a componetn that has been + * added by ecs_set_trait. + * + * @param world The world. + * @param e The entity. + * @param component The component to which the trait was added. + * @param trait The trait that was added. + */ +#define ecs_get_trait(world, entity, component, trait)\ + ((trait*)ecs_get_w_entity(world, entity, ecs_trait(ecs_typeid(component), ecs_typeid(trait)))) + +/** Get trait tag for component. + * This operation obtains the value of a trait for a componetn that has been + * added by ecs_set_trait. + * + * @param world The world. + * @param e The entity. + * @param trait The trait that was added. + * @param component The component to which the trait was added. + */ +#define ecs_get_trait_tag(world, entity, trait, component)\ + ((component*)ecs_get_w_entity(world, entity, ecs_trait(ecs_typeid(component), trait))) + +/** Get case for switch. + * This operation gets the current case for the specified switch. If the current + * switch is not set for the entity, the operation will return 0. + * + * @param world The world. + * @param e The entity. + * @param sw The switch for which to obtain the case. + * @return The current case for the specified switch. + */ +FLECS_API +ecs_entity_t ecs_get_case( + ecs_world_t *world, + ecs_entity_t e, + ecs_entity_t sw); + +/** @} */ + +/** + * @defgroup deleting Deleting Entities and components + * @{ + */ + +/** Clear all components. + * This operation will clear all components from an entity but will not delete + * the entity itself. This effectively prevents the entity id from being + * recycled. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity); + +/** Delete an entity. + * This operation will delete an entity and all of its components. The entity id + * will be recycled. Repeatedly calling ecs_delete without ecs_new, + * ecs_new_w_entity or ecs_new_w_type will cause a memory leak as it will cause + * the list with ids that can be recycled to grow unbounded. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity); + + +/** Delete children of an entity. + * This operation deletes all children of a parent entity. If a parent has no + * children this operation has no effect. + * + * @param world The world. + * @param parent The parent entity. + */ +FLECS_API +void ecs_delete_children( + ecs_world_t *world, + ecs_entity_t parent); + +/** @} */ + +/** + * @defgroup getting Getting Components + * @{ + */ + +/** Get an immutable pointer to a component. + * This operation obtains a const pointer to the requested component. The + * operation accepts the component entity id. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +const void* ecs_get_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component); + +/** Get an immutable pointer to a component. + * Same as ecs_get_w_entity, but accepts the typename of a component. + * + * @param world The world. + * @param entity The entity. + * @param component The component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +#define ecs_get(world, entity, component)\ + ((const component*)ecs_get_w_entity(world, entity, ecs_typeid(component))) + +/* -- Get cached pointer -- */ + +/** Get an immutable reference to a component. + * This operation is similar to ecs_get_w_entity but it stores temporary + * information in a `ecs_ref_t` value which allows subsequent lookups to be + * faster. + * + * @param world The world. + * @param ref Pointer to a ecs_ref_t value. Must be initialized. + * @param entity The entity. + * @param component The entity id of the component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +const void* ecs_get_ref_w_entity( + ecs_world_t *world, + ecs_ref_t *ref, + ecs_entity_t entity, + ecs_entity_t component); + +/** Get an immutable reference to a component. + * Same as ecs_get_ref_w_entity, but accepts the typename of a component. + * + * @param world The world. + * @param ref Pointer to a ecs_ref_t value. Must be initialized. + * @param entity The entity. + * @param component The component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +#define ecs_get_ref(world, ref, entity, component)\ + ((const component*)ecs_get_ref_w_entity(world, ref, entity, ecs_typeid(component))) + +/** Get a mutable pointer to a component. + * This operation is similar to ecs_get_w_entity but it returns a mutable + * pointer. If this operation is invoked from inside a system, the entity will + * be staged and a pointer to the staged component will be returned. + * + * If the entity did not yet have the component, the component will be added by + * this operation. In this case the is_added out parameter will be set to true. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component to obtain. + * @param is_added Out parameter that returns true if the component was added. + * @return The component pointer. + */ +FLECS_API +void* ecs_get_mut_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + bool *is_added); + +/** Get a mutable pointer to a component. + * Same as ecs_get_mut_w_entity but accepts a component typename. + * + * @param world The world. + * @param entity The entity. + * @param component The component to obtain. + * @param is_added Out parameter that returns true if the component was added. + * @return The component pointer. + */ +#define ecs_get_mut(world, entity, component, is_added)\ + ((component*)ecs_get_mut_w_entity(world, entity, ecs_typeid(component), is_added)) + +/** Signal that a component has been modified. + * This operation allows an application to signal to Flecs that a component has + * been modified. As a result, OnSet systems will be invoked. + * + * This operation is commonly used together with ecs_get_mut. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component that was modified. + */ +FLECS_API +void ecs_modified_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component); + +/** Signal that a component has been modified. + * Same as ecs_modified_w_entity but accepts a component typename. + * + * @param world The world. + * @param entity The entity. + * @param component The component that was modified. + */ +#define ecs_modified(world, entity, component)\ + ecs_modified_w_entity(world, entity, ecs_typeid(component)) + + +/** @} */ + +/** + * @defgroup setting Setting Components + * @{ + */ + +/** Set the value of a component. + * This operation allows an application to set the value of a component. The + * operation is equivalent to calling ecs_get_mut and ecs_modified. + * + * If the provided entity is 0, a new entity will be created. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component to set. + * @param size The size of the pointer to the value. + * @param ptr The pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +FLECS_API +ecs_entity_t ecs_set_ptr_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + size_t size, + const void *ptr); + +/** Set the value of a component. + * Same as ecs_set_ptr_w_entity, but accepts a component typename and + * automatically determines the type size. + * + * @param world The world. + * @param entity The entity. + * @param component The component to set. + * @param size The size of the pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +#define ecs_set_ptr(world, entity, component, ptr)\ + ecs_set_ptr_w_entity(world, entity, ecs_typeid(component), sizeof(component), ptr) + +/* Conditionally skip macro's as compound literals and variadic arguments are + * not supported in C89 */ +#ifndef FLECS_LEGACY + +/** Set the value of a component. + * Same as ecs_set_ptr, but accepts a value instead of a pointer to a value. + * + * @param world The world. + * @param entity The entity. + * @param component The component to set. + * @param size The size of the pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +#define ecs_set(world, entity, component, ...)\ + ecs_set_ptr_w_entity(world, entity, ecs_typeid(component), sizeof(component), &(component)__VA_ARGS__) + +#endif + +/** @} */ + +/** + * @defgroup singleton Singleton components + * @{ + */ + +#define ecs_singleton_get(world, comp)\ + ecs_get(world, ecs_typeid(comp), comp) + +#ifndef FLECS_LEGACY +#define ecs_singleton_set(world, comp, ...)\ + ecs_set(world, ecs_typeid(comp), comp, __VA_ARGS__) +#endif + +#define ecs_singleton_get_mut(world, comp)\ + ecs_get_mut(world, ecs_typeid(comp), comp, NULL) + +#define ecs_singleton_modified(world, comp)\ + ecs_modified(world, ecs_typeid(comp), comp) + +/** + * @defgroup testing Testing Components + * @{ + */ + +/** Test if an entity has an entity. + * This operation returns true if the entity has the provided entity in its + * type. + * + * @param world The world. + * @param entity The entity. + * @param to_check The entity to test for. + * @return True if the entity has the entity, false if not. + */ +FLECS_API +bool ecs_has_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t to_check); + +/** Test if an entity has a type. + * This operation returns true if the entity has the provided type in its + * type. + * + * @param world The world. + * @param entity The entity. + * @param type The type to test for. + * @return True if the entity has the type, false if not. + */ +FLECS_API +bool ecs_has_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +/** Test if an entity has a component, type or tag. + * This operation returns true if the entity has the provided component, type or + * tag in its type. + * + * @param world The world. + * @param entity The entity. + * @param type The component, type or tag to test for. + * @return True if the entity has the type, false if not. + */ +#define ecs_has(world, entity, type)\ + ecs_has_type(world, entity, ecs_type(type)) + +/** Test if an entity owns component, type or tag. + * This operation is similar to ecs_has, but will return false if the entity + * does not own the component, which is the case if the component is defined on + * a base entity with an INSTANCEOF role. + * + * @param world The world. + * @param entity The entity. + * @param type The component, type or tag to test for. + * @return True if the entity owns the component, type or tag, false if not. + */ +#define ecs_owns(world, entity, type, owned)\ + ecs_type_owns_type(world, ecs_get_type(world, entity), ecs_type(type), owned) + +/** Test if an entity owns an entity. + * This operation is similar to ecs_has, but will return false if the entity + * does not own the entity, which is the case if the entity is defined on + * a base entity with an INSTANCEOF role. + * + * @param world The world. + * @param entity The entity. + * @param type The entity to test for. + * @return True if the entity owns the entity, false if not. + */ +#define ecs_owns_entity(world, entity, has, owned)\ + ecs_type_owns_entity(world, ecs_get_type(world, entity), has, owned) + + +/** @} */ + +/** + * @defgroup metadata Entity Metadata + * @{ + */ + +/** Test whether an entity is alive. + * + * @param world The world. + * @param e The entity. + * @return True if the entity is alive, false if the entity is not alive. + */ +FLECS_API +bool ecs_is_alive( + ecs_world_t *world, + ecs_entity_t e); + +/** Test whether an entity exists. + * Similar as ecs_is_alive, but ignores entity generation count. + * + * @param world The world. + * @param e The entity. + * @return True if the entity exists, false if the entity does not exist. + */ +FLECS_API +bool ecs_exists( + ecs_world_t *world, + ecs_entity_t e); + +/** Get the type of an entity. + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no components. + */ +FLECS_API +ecs_type_t ecs_get_type( + ecs_world_t *world, + ecs_entity_t entity); + +/** Get the typeid of an entity. + * + * @param world The world. + * @param entity The entity. + * @return The typeid of the entity. + */ +FLECS_API +ecs_entity_t ecs_get_typeid( + ecs_world_t *world, + ecs_entity_t e); + +/** Get the name of an entity. + * This will return the name as specified in the EcsName component. + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no name. + */ +FLECS_API +const char* ecs_get_name( + ecs_world_t *world, + ecs_entity_t entity); + +/** Convert type role to string. + * This operation converts a type role to a string. + * + * @param world The world. + * @param entity The entity containing the type role. + * @return The type role string, or NULL if no type role is provided. + */ +FLECS_API +const char* ecs_role_str( + ecs_entity_t entity); + +/** Convert entity identifier to string. + * This operation interprets type roles and translates them to a string. + * + * @param world The world. + * @param entity The entity to convert to a string. + * @param buffer The buffer in which to store the string. + * @param buffer_len The length of the provided buffer. + * @return The number of characters required to write the string. + */ +FLECS_API +size_t ecs_entity_str( + ecs_world_t *world, + ecs_entity_t entity, + char *buffer, + size_t buffer_len); + +/** Get the parent of an entity. + * This will return a parent of the entity that has the specified component. If + * the component is 0, the operation will return the first parent that it finds + * in the entity type (an entity with a CHILDOF role). + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of a component that the parent must have. + * @return The parent of the entity, 0 if no parent was found. + */ +FLECS_API +ecs_entity_t ecs_get_parent_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component); + +/** Get the parent of an entity. + * Same as ecs_get_parent_w_entity but accepts a component typename. + * + * @param world The world. + * @param entity The entity. + * @param component A component that the parent must have. + * @return The parent of the entity, 0 if no parent was found. + */ +#define ecs_get_parent(world, entity, component)\ + ecs_get_parent_w_entity(world, entity, ecs_typeid(component)) + + +/** Enable or disable an entity. + * This operation enables or disables an entity by adding or removing the + * EcsDisabled tag. A disabled entity will not be matched with any systems, + * unless the system explicitly specifies the EcsDisabled tag. + * + * @param world The world. + * @param entity The entity to enable or disable. + * @param enabled true to enable the entity, false to disable. + */ +FLECS_API +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled); + +/** Count entities that have an entity. + * Returns the number of entities that have the specified entity. + * + * @param world The world. + * @param entity The entity. + * @return The number of entities that have the entity. + */ +FLECS_API +int32_t ecs_count_entity( + ecs_world_t *world, + ecs_entity_t entity); + +/** Count entities that have a type. + * Returns the number of entities that have the specified type. + * + * @param world The world. + * @param type The type. + * @return The number of entities that have the type. + */ +FLECS_API +int32_t ecs_count_type( + ecs_world_t *world, + ecs_type_t type); + +/** Count entities that have a component, type or tag. + * Returns the number of entities that have the specified component, type or tag. + * + * @param world The world. + * @param type The component, type or tag. + * @return The number of entities that have the component, type or tag. + */ +#define ecs_count(world, type)\ + ecs_count_type(world, ecs_type(type)) + +/** Count entities that match a filter. + * Returns the number of entities that match the specified filter. + * + * @param world The world. + * @param type The type. + * @return The number of entities that match the specified filter. + */ +FLECS_API +int32_t ecs_count_w_filter( + ecs_world_t *world, + const ecs_filter_t *filter); + + +/** @} */ + +/** + * @defgroup lookup Lookups + * @{ + */ + +/** Lookup an entity by name. + * Returns an entity that matches the specified name. Only looks for entities in + * the current scope (root if no scope is provided). + * + * @param world The world. + * @param name The entity name. + * @return The entity with the specified name, or 0 if no entity was found. + */ +FLECS_API +ecs_entity_t ecs_lookup( + ecs_world_t *world, + const char *name); + +/** Lookup a child entity by name. + * Returns an entity that matches the specified name. Only looks for entities in + * the provided parent. If no parent is provided, look in the current scope ( + * root if no scope is provided). + * + * @param world The world. + * @param name The entity name. + * @return The entity with the specified name, or 0 if no entity was found. + */ +FLECS_API +ecs_entity_t ecs_lookup_child( + ecs_world_t *world, + ecs_entity_t parent, + const char *name); + +/** Lookup an entity from a path. + * Lookup an entity from a provided path, relative to the provided parent. The + * operation will use the provided separator to tokenize the path expression. If + * the provided path contains the prefix, the search will start from the root. + * + * If the entity is not found in the provided parent, the operation will + * continue to search in the parent of the parent, until the root is reached. If + * the entity is still not found, the lookup will search in the flecs.core + * scope. If the entity is not found there either, the function returns 0. + * + * @param world The world. + * @param parent The entity from which to resolve the path. + * @param path The path to resolve. + * @param sep The path separator. + * @param prefix The path prefix. + * @return The entity if found, else 0. + */ +FLECS_API +ecs_entity_t ecs_lookup_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix); + +/** Lookup an entity from a path. + * Same as ecs_lookup_path_w_sep, but with defaults for the separator and + * prefix. These defaults are used when looking up identifiers in a type or + * signature expression. + * + * @param world The world. + * @param parent The entity from which to resolve the path. + * @param path The path to resolve. + * @return The entity if found, else 0. + */ +#define ecs_lookup_path(world, parent, path)\ + ecs_lookup_path_w_sep(world, parent, path, ".", NULL) + +/** Lookup an entity from a full path. + * Same as ecs_lookup_pat, but searches from the current scope, or root scope + * if no scope is set. + * + * @param world The world. + * @param path The path to resolve. + * @return The entity if found, else 0. + */ +#define ecs_lookup_fullpath(world, path)\ + ecs_lookup_path_w_sep(world, 0, path, ".", NULL) + +/** Lookup an entity by its symbol name. + * This looks up an entity by the symbol name that was provided in EcsName. The + * operation does not take into account scoping, which means it will search all + * entities that have an EcsName. + * + * This operation can be useful to resolve, for example, a type by its C + * identifier, which does not include the Flecs namespacing. + */ +FLECS_API +ecs_entity_t ecs_lookup_symbol( + ecs_world_t *world, + const char *name); + +/* Add alias for entity to global scope */ +FLECS_API +void ecs_use( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +/** @} */ + +/** + * @defgroup paths Paths + * @{ + */ + +/** Get a path identifier for an entity. + * This operation creates a path that contains the names of the entities from + * the specified parent to the provided entity, separated by the provided + * separator. If no parent is provided the path will be relative to the root. If + * a prefix is provided, the path will be prefixed by the prefix. + * + * If the parent is equal to the provided child, the operation will return an + * empty string. If a nonzero component is provided, the path will be created by + * looking for parents with that component. + * + * The returned path should be freed by the application. + * + * @param world The world. + * @param parent The entity from which to create the path. + * @param child The entity to which to create the path. + * @param component The component of the parent. + * @return The relative entity path. + */ +FLECS_API +char* ecs_get_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + ecs_entity_t component, + const char *sep, + const char *prefix); + +/** Get a path identifier for an entity. + * Same as ecs_get_path_w_sep, but with default values for the separator and + * prefix. These defaults are used throughout Flecs whenever identifiers are + * used in type or signature expressions. + * + * @param world The world. + * @param parent The entity from which to create the path. + * @param child The entity to which to create the path. + * @return The relative entity path. + */ +#define ecs_get_path(world, parent, child)\ + ecs_get_path_w_sep(world, parent, child, 0, ".", NULL) + +/** Get a full path for an entity. + * Same as ecs_get_path, but with default values for the separator and + * prefix, and the path is created from the current scope, or root if no scope + * is provided. + * + * @param world The world. + * @param child The entity to which to create the path. + * @return The entity path. + */ +#define ecs_get_fullpath(world, child)\ + ecs_get_path_w_sep(world, 0, child, 0, ".", NULL) + +/** Find or create entity from path. + * This operation will find or create an entity from a path, and will create any + * intermediate entities if required. If the entity already exists, no entities + * will be created. + * + * If the path starts with the prefix, then the entity will be created from the + * root scope. + * + * @param world The world. + * @param parent The entity relative to which the entity should be created. + * @param path The path to create the entity for. + * @param sep The separator used in the path. + * @param prefix The prefix used in the path. + * @return The entity. + */ +FLECS_API +ecs_entity_t ecs_new_from_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix); + +/** Find or create entity from path. + * Same as ecs_new_from_path_w_sep, but with defaults for sep and prefix. + * + * @param world The world. + * @param parent The entity relative to which the entity should be created. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_new_from_path(world, parent, path)\ + ecs_new_from_path_w_sep(world, parent, path, ".", NULL) + +/** Find or create entity from full path. + * Same as ecs_new_from_path, but entity will be created from the current scope, + * or root scope if no scope is set. + * + * @param world The world. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_new_from_fullpath(world, path)\ + ecs_new_from_path_w_sep(world, 0, path, ".", NULL) + +/** Add specified path to entity. + * This operation is similar to ecs_new_from_path, but will instead add the path + * to an existing entity. + * + * If an entity already exists for the path, it will be returned instead. + * + * @param world The world. + * @param entity The entity to which to add the path. + * @param parent The entity relative to which the entity should be created. + * @param path The path to create the entity for. + * @param sep The separator used in the path. + * @param prefix The prefix used in the path. + * @return The entity. + */ +FLECS_API +ecs_entity_t ecs_add_path_w_sep( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix); + +/** Add specified path to entity. + * Same as ecs_add_from_path_w_sep, but with defaults for sep and prefix. + * + * @param world The world. + * @param entity The entity to which to add the path. + * @param parent The entity relative to which the entity should be created. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_add_path(world, entity, parent, path)\ + ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) + +/** Add specified path to entity. + * Same as ecs_add_from_path, but entity will be created from the current scope, + * or root scope if no scope is set. + * + * @param world The world. + * @param entity The entity to which to add the path. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_add_fullpath(world, entity, path)\ + ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) + + +/** @} */ + +/** + * @defgroup scopes Scopes + * @{ + */ + +/** Does entity have children. + * + * @param world The world + * @param entity The entity + * @return True if the entity has children, false if not. + */ +FLECS_API +int32_t ecs_get_child_count( + ecs_world_t *world, + ecs_entity_t entity); + +/** Return a scope iterator. + * A scope iterator iterates over all the child entities of the specified + * parent. + * + * @param world The world. + * @param parent The parent entity for which to iterate the children. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_scope_iter( + ecs_world_t *world, + ecs_entity_t parent); + +/** Return a filtered scope iterator. + * Same as ecs_scope_iter, but results will be filtered. + * + * @param world The world. + * @param parent The parent entity for which to iterate the children. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_scope_iter_w_filter( + ecs_world_t *world, + ecs_entity_t parent, + ecs_filter_t *filter); + +/** Progress the scope iterator. + * This operation progresses the scope iterator to the next table. The iterator + * must have been initialized with `ecs_scope_iter`. This operation must be + * invoked at least once before interpreting the contents of the iterator. + * + * @param it The iterator + * @return True if more data is available, false if not. + */ +FLECS_API +bool ecs_scope_next( + ecs_iter_t *it); + +/** Set the current scope. + * This operation sets the scope of the current stage to the provided entity. + * As a result new entities will be created in this scope, and lookups will be + * relative to the provided scope. + * + * It is considered good practice to restore the scope to the old value. + * + * @param world The world. + * @param scope The entity to use as scope. + * @return The previous scope. + */ +FLECS_API +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope); + +/** Get the current scope. + * Get the scope set by ecs_set_scope. If no scope is set, this operation will + * return 0. + * + * @param world The world. + * @return The current scope. + */ +FLECS_API +ecs_entity_t ecs_get_scope( + ecs_world_t *world); + +/** Set a name prefix for newly created entities. + * This is a utility that lets C modules use prefixed names for C types and + * C functions, while using names for the entity names that do not have the + * prefix. The name prefix is currently only used by ECS_COMPONENT. + * + * @param world The world. + * @param prefix The name prefix to use. + * @return The previous prefix. + */ +FLECS_API +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix); + +/** @} */ + +/** + * @defgroup filters Filters + * @{ + */ + +/** Return a filter iterator. + * A filter iterator lets an application iterate over entities that match the + * specified filter. If NULL is provided for the filter, the iterator will + * iterate all tables in the world. + * + * @param world The world. + * @param filter The filter. + * @return An iterator that can be used with ecs_filter_next. + */ +FLECS_API +ecs_iter_t ecs_filter_iter( + ecs_world_t *world, + const ecs_filter_t *filter); + +/** Iterate tables matched by filter. + * This operation progresses the filter iterator to the next table. The + * iterator must have been initialized with `ecs_filter_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param it The iterator + * @return True if more data is available, false if not. + */ +FLECS_API +bool ecs_filter_next( + ecs_iter_t *iter); + + +/** @} */ + +/** + * @defgroup queries Queries + * @{ + */ + +/** Create a query. + * This operation creates a query. Queries are used to iterate over entities + * that match a signature expression and are the fastest way to find and iterate + * over entities and their components. + * + * Queries should be created once, and reused multiple times. While iterating a + * query is a cheap operation, creating and deleting a query is expensive. The + * reason for this is that queries are "prematched", which means that a query + * stores state about which entities (or rather, tables) match with the query. + * Building up this state happens during query creation. + * + * Once a query is created, matching only happens when new tables are created. + * In most applications this is an infrequent process, since it only occurs when + * a new combination of components is introduced. While matching is expensive, + * it is importent to note that matching does not happen on a per-entity basis, + * but on a per-table basis. This means that the average time spent on matching + * per frame should rapidly approach zero over the lifetime of an application. + * + * A query provides direct access to the component arrays. When an application + * creates/deletes entities or adds/removes components, these arrays can shift + * component values around, or may grow in size. This can cause unexpected or + * undefined behavior to occur if these operations are performed while + * iterating. To prevent this from happening an application should either not + * perform these operations while iterating, or use deferred operations (see + * ecs_defer_begin and ecs_defer_end). + * + * Queries can be created and deleted dynamically. If a query was not deleted + * (using ecs_query_free) before the world is deleted, it will be deleted + * automatically. + * + * @param world The world. + * @param sig The query signature expression. + * @return The new query. + */ +FLECS_API +ecs_query_t* ecs_query_new( + ecs_world_t *world, + const char *sig); + +/** Create a subquery. + * A subquery is just like a regular query, except that it is matched against + * the matched tables of a parent query. Reducing the number of global (normal) + * queries can improve performance, as new archetypes have to be matched against + * fewer queries. + * + * Subqueries are cheaper to create than regular queries, because the initial + * set of tables they have to match against is smaller. This makes subqueries + * more suitable for creating while the simulation. + * + * Subqueries are not registered with tables directly, and instead receive new + * table notifications from their parent query. This means that there is less + * administrative overhead associated with subqueries. + * + * Subqueries can be nested, which allows for the creation of increasingly more + * specific query hierarchies that are considerably more efficient than when all + * queries would be created as global queries. + * + * @param world The world. + * @param parent The parent query. + * @param sig The query signature expression. + * @return The new subquery. + */ +FLECS_API +ecs_query_t* ecs_subquery_new( + ecs_world_t *world, + ecs_query_t *parent, + const char *sig); + +/** Cleanup a query. + * This operation frees a query. + * + * @param query The query. + */ +FLECS_API +void ecs_query_free( + ecs_query_t *query); + +/** Return a query iterator. + * A query iterator lets an application iterate over entities that match the + * specified query. If a sorting function is specified, the query will check + * whether a resort is required upon creating the iterator. + * + * Creating a query iterator is a cheap operation that does not allocate any + * resources. An application does not need to deinitialize or free a query + * iterator before it goes out of scope. + * + * To iterate the iterator, an application should use ecs_query_next to progress + * the iterator and test if it has data. + * + * Query iteration requires an outer and an inner loop. The outer loop uses + * ecs_query_next to test if new tables are available. The inner loop iterates + * the entities in the table, and is usually a for loop that uses iter.count to + * loop through the entities and component arrays. + * + * The two loops are necessary because of how data is stored internally. + * Entities are grouped by the components they have, in tables. A single query + * can (and often does) match with multiple tables. Because each table has its + * own set of arrays, an application has to reobtain pointers to those arrays + * for each matching table. + * + * @param query The query to iterate. + * @return The query iterator. + */ +FLECS_API +ecs_iter_t ecs_query_iter( + ecs_query_t *query); + +/** Iterate over a query. + * This operation is similar to ecs_query_iter, but starts iterating from a + * specified offset, and will not iterate more than limit entities. + * + * @param query The query to iterate. + * @param offset The number of entities to skip. + * @param limit The maximum number of entities to iterate. + * @return The query iterator. + */ +FLECS_API +ecs_iter_t ecs_query_iter_page( + ecs_query_t *query, + int32_t offset, + int32_t limit); + +/** Progress the query iterator. + * This operation progresses the query iterator to the next table. The + * iterator must have been initialized with `ecs_query_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param iter The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next( + ecs_iter_t *iter); + +/** Progress the query iterator with filter. + * This operation is the same as ecs_query_next, but accepts a filter as an + * argument. Entities not matching the filter will be skipped by the iterator. + * + * @param iter The iterator. + * @param filter The filter to apply to the iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next_w_filter( + ecs_iter_t *iter, + const ecs_filter_t *filter); + +/** Progress the query iterator for a worker thread. + * This operation is similar to ecs_query_next, but provides the ability to + * divide entities up across multiple worker threads. The operation accepts a + * current thread id and a total thread id, which is used to determine which + * subset of entities should be assigned to the current thread. + * + * Current should be less than total, and there should be as many as total + * threads. If there are less entities in a table than there are threads, only + * as many threads as there are entities will iterate that table. + * + * @param it The iterator. + * @param current Thread id of current thread. + * @param total Total number of threads. + * @returns True if more data is available, false if not. + */ +bool ecs_query_next_worker( + ecs_iter_t *it, + int32_t current, + int32_t total); + +/** Sort the output of a query. + * This enables sorting of entities across matched tables. As a result of this + * operation, the order of entities in the matched tables may be changed. + * Resorting happens when a query iterator is obtained, and only if the table + * data has changed. + * + * If multiple queries that match the same (sub)set of tables specify different + * sorting functions, resorting is likely to happen every time an iterator is + * obtained, which can significantly slow down iterations. + * + * The sorting function will be applied to the specified component. Resorting + * only happens if that component has changed, or when the entity order in the + * table has changed. If no component is provided, resorting only happens when + * the entity order changes. + * + * @param world The world. + * @param query The query. + * @param component The component used to sort. + * @param compare The compare function used to sort the components. + */ +FLECS_API +void ecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t component, + ecs_compare_action_t compare); + +/** Group and sort matched tables. + * Similar yo ecs_query_order_by, but instead of sorting individual entities, this + * operation only sorts matched tables. This can be useful of a query needs to + * enforce a certain iteration order upon the tables it is iterating, for + * example by giving a certain component or tag a higher priority. + * + * The sorting function assigns a "rank" to each type, which is then used to + * sort the tables. Tables with higher ranks will appear later in the iteration. + * + * Resorting happens when a query iterator is obtained, and only if the set of + * matched tables for a query has changed. If table sorting is enabled together + * with entity sorting, table sorting takes precedence, and entities will be + * sorted within each set of tables that are assigned the same rank. + * + * @param world The world. + * @param query The query. + * @param component The component used to determine the group rank. + * @param rank_action The rank action. + */ +FLECS_API +void ecs_query_group_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t component, + ecs_rank_type_action_t rank_action); + +/** Returns whether the query data changed since the last iteration. + * This operation must be invoked before obtaining the iterator, as this will + * reset the changed state. The operation will return true after: + * - new entities have been matched with + * - matched entities were deleted + * - matched components were changed + * + * @param query The query. + * @return true if entities changed, otherwise false. + */ +FLECS_API +bool ecs_query_changed( + ecs_query_t *query); + +/** Returns whether query is orphaned. + * When the parent query of a subquery is deleted, it is left in an orphaned + * state. The only valid operation on an orphaned query is deleting it. Only + * subqueries can be orphaned. + * + * @param query The query. + * @return true if query is orphaned, otherwise false. + */ +FLECS_API +bool ecs_query_orphaned( + ecs_query_t *query); + +/** @} */ + +/** + * @defgroup iterator Iterators + * @{ + */ + +/** Obtain column data. + * This operation is to be used to obtain a component array for a specific + * column in the system or query signature. The column is identified by the + * provided index. For example, if this is the provided signature: + * + * Position, Velocity + * + * Position is at index 1, and Velocity is at index 2. + * + * This operation may return NULL if the column is optional, and the current + * table does not have the data. Additionally, if the column points to a shared + * component or a reference, the returned value should be interpreted as a + * pointer instead of an array. + * + * The provided size must match the size of the component, otherwise the + * function may fail. + * + * @param it The iterator. + * @param size The size of the component. + * @param column The index identifying the column in a signature. + * @return A pointer to the column data. + */ +FLECS_API +void* ecs_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column); + +/** Obtain column data. + * This operation is similar to ecs_column_w_size, except that it accepts the + * component typename. + * + * @param it The iterator. + * @param type The typename of the component for which to obtain the data. + * @param column The index identifying the column in a signature. + * @return A pointer to the column data. + */ +#define ecs_column(it, type, column)\ + ((type*)ecs_column_w_size(it, sizeof(type), column)) + +/** Get column index by name. + * This function obtains a column index by name. This function can only be used + * if a query signature contains names. + * + * @param it The iterator. + * @param name The column name. + * @return Index of the column (to be used with ecs_column_* functions). + */ +FLECS_API +int32_t ecs_column_index_from_name( + const ecs_iter_t *it, + const char *name); + +/** Test if column is owned or not. + * The following signature shows an example of one owned components and two + * components that are not owned by the current entity: + * + * Position, PARENT:Velocity, MyEntity:Mass + * + * Position is an owned component. Velocity and Mass both belong to a different + * entity. This operation will return false for Position, and true for Velocity + * and Mass. If a component is matched from a prefab, this operation will also + * return false. + * + * @param it The it parameter passed into the system. + * @param index The index identifying the column in a system signature. + * @return True if column is owned, false if column is not. + */ +FLECS_API +bool ecs_is_owned( + const ecs_iter_t *it, + int32_t column); + +/** Obtain a single element. + * This operation is similar to ecs_column, but instead of an array it obtains + * a single element from a component array. The advantage of using ecs_element + * is that a system can be agnostic towards whether a component is owned or not, + * at the cost of some additional performance overhead. + * + * @param it The iterator. + * @param size The component size. + * @param column The index identifying the column in a signature. + * @param row The current row in the table. + * @return A pointer to the current element. + */ +FLECS_API +void *ecs_element_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column, + int32_t row); + +/** Obtain a single element. + * Same as ecs_element_w_size, but allows specifying a typename instead of a + * size. + * + * @param it The iterator. + * @param type The column type. + * @param column The index identifying the column in a signature. + * @param row The current row in the table. + * @return A pointer to the current element. + */ +#define ecs_element(it, type, column, row)\ + ((type*)ecs_element_w_size(it, sizeof(type), column, row)) + +/** Obtain the source of a signature column. + * This operation returns the source of a signature column. By default this will + * return 0 for regular columns, but for columns where the components are + * provided by entities other than the entity being iterated over, this will + * return the source of the component. + * + * @param it Pointer to the it object passed into the system callback. + * @param column The index identifying the column in a signature. + * @return The source entity for the column. + */ +FLECS_API +ecs_entity_t ecs_column_source( + const ecs_iter_t *it, + int32_t column); + +/** Obtain the entity id of the signature column. + * This operation returns the entity id of the component or tag used in the + * system signature. For example, when provided this signature: + * + * Position, Velocity + * + * ecs_column_entity(world, 1) will return the component handle for Position and + * ecs_column_entity(world, 2) will return the componnet handle for Velocity. + * + * @param it The iterator. + * @param column The index identifying the column in a signature. + * @return The entity id of the signature column. + */ +FLECS_API +ecs_entity_t ecs_column_entity( + const ecs_iter_t *it, + int32_t column); + +/** Obtain the type of a column from inside a system. + * This operation is equivalent to ecs_column_entity, except that it returns + * a type, instead of an entity handle. Invoking this function is the same as + * doing: + * + * ecs_type_from_entity( ecs_column_entity(it, index)); + * + * @param it The iterator. + * @param column The index identifying the column in a signature. + * @return The type for the specified column, or NULL if failed. + */ +FLECS_API +ecs_type_t ecs_column_type( + const ecs_iter_t *it, + int32_t column); + +/** Get the size of the component of the specified column. + * + * @param it The iterator. + * @param column The column for which to obtain the size. + */ +FLECS_API +size_t ecs_column_size( + const ecs_iter_t *it, + int32_t column); + +/** Is the column readonly. + * This operation returns if the column is a readonly column. Readonly columns + * are marked in the system signature with the [in] modifier. + * + * @param it Pointer to the it object passed into the system callback. + * @param column An index identifying the column. + * @return True if the column is readonly, false otherwise. */ +FLECS_API +bool ecs_is_readonly( + const ecs_iter_t *it, + int32_t column); + +/** Get type of table that system is currently iterating over. + * This will return the type for all entities that are currently being iterated + * over, until ecs_iter_next is invoked. + * + * @param it The iterator. + * @return The type of the current table. + */ +FLECS_API +ecs_type_t ecs_iter_type( + const ecs_iter_t *it); + +/** Get component array from table. + * In some cases an application may require access to the table component arrays + * directly instead of going through the signature to table mapping. A typical + * scenario where this would be used is when using a filter iterator, where + * there is no signature, and thus ecs_column cannot be used. + * + * @param it The iterator. + * @param column The index identifying the column in a table. + * @return The component array corresponding to the column index. + */ +FLECS_API +void* ecs_table_column( + const ecs_iter_t *it, + int32_t column); + +/** Get the size of a table column. + * + * @param it The iterator. + * @param column The column for which to obtain the size. + */ +FLECS_API +size_t ecs_table_column_size( + const ecs_iter_t *it, + int32_t column); + +/** Get the index of the table column for a component. + * + * @param it The iterator. + * @param component The component for which to obtain the index. + */ +FLECS_API +int32_t ecs_table_component_index( + const ecs_iter_t *it, + ecs_entity_t component); + +/** Get a strongly typed pointer to a column (owned or shared). */ +#define ECS_COLUMN(it, type, id, column)\ + ECS_ENTITY_VAR(type) = ecs_column_entity(it, column);\ + ECS_TYPE_VAR(type) = ecs_column_type(it, column);\ + type *id = ecs_column(it, type, column);\ + (void)ecs_typeid(type);\ + (void)ecs_type(type);\ + (void)id + +/** Get a strongly typed pointer to a column (owned or shared). */ +#define ECS_CONST_COLUMN(it, type, id, column)\ + const type *id = ecs_const_column(it, type, column) + +/** Obtain a handle to the component of a column */ +#define ECS_COLUMN_COMPONENT(it, id, column)\ + ECS_ENTITY_VAR(id) = ecs_column_entity(it, column);\ + ECS_TYPE_VAR(id) = ecs_column_type(it, column);\ + (void)ecs_typeid(id);\ + (void)ecs_type(id) + +/** Obtain a handle to the entity of a column */ +#define ECS_COLUMN_ENTITY(it, id, column)\ + ecs_entity_t id = ecs_column_entity(it, column);\ + ECS_TYPE_VAR(id) = ecs_column_type(it, column);\ + (void)id;\ + (void)ecs_type(id) + +/** Utility macro for importing all handles for a module from a system column */ +#define ECS_IMPORT_COLUMN(it, module, column) \ + module *ecs_module_ptr(module) = ecs_column(it, module, column);\ + ecs_assert(ecs_module_ptr(module) != NULL, ECS_MODULE_UNDEFINED, #module);\ + ecs_assert(!ecs_is_owned(it, column), ECS_COLUMN_IS_NOT_SHARED, NULL);\ + module ecs_module(module) = *ecs_module_ptr(module);\ + module##ImportHandles(ecs_module(module)) + + +/** @} */ + +/** + * @defgroup staging Staging + * @{ + */ + +/** Begin frame. */ +FLECS_API +FLECS_FLOAT ecs_frame_begin( + ecs_world_t *world, + FLECS_FLOAT delta_time); + +/** End frame. */ +FLECS_API +void ecs_frame_end( + ecs_world_t *world); + +/** Begin staging. + * When staging is enabled, modifications to entities are stored to a stage. + * This ensures that arrays are not modified while iterating. Modifications are + * merged back to the "main stage" when ecs_staging_end is invoked. + * + * @param world The world + * @return Whether world is currently staged. + */ +FLECS_API +bool ecs_staging_begin( + ecs_world_t *world); + +/** End staging. + * If any data was staged, this operation will merge that data back to the main + * stage. + * + * @param world The world + */ +FLECS_API +void ecs_staging_end( + ecs_world_t *world); + +/** Manually merge. + * When automerging is set to false, an application can invoke this operation to + * force merging all stages. + * + * @param world The world. + */ +FLECS_API +void ecs_merge( + ecs_world_t *world); + +/** Defer operations until end of frame. + * When this operation is invoked while iterating, operations inbetween the + * defer_begin and defer_end operations are executed at the end of the frame. + * + * This operation is thread safe. + * + * @param world The world. + * @return true if the world was already in deferred mode, false if not. + */ +FLECS_API +bool ecs_defer_begin( + ecs_world_t *world); + +/** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + * + * @param world The world. + * @return true if world changed from deferred mode to non-deferred mode. + */ +FLECS_API +bool ecs_defer_end( + ecs_world_t *world); + +/** Enable / disable automerging. + * When automerging is enabled, running a pipeline will automatically merge when + * necessary. With automerging disabled, merging will not happen unless the + * application manually invokes ecs_merge. + * + * @param world The world. + */ +FLECS_API +void ecs_set_automerge( + ecs_world_t *world, + bool auto_merge); + +/** @} */ + +/* Optional modules */ +#ifdef FLECS_SYSTEM +#ifdef FLECS_SYSTEM +#define FLECS_MODULE + +#ifdef FLECS_MODULE + +/** + * @file module.h + * @brief Module API. + */ + +#ifndef FLECS_MODULE_H +#define FLECS_MODULE_H + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Module API +//////////////////////////////////////////////////////////////////////////////// + +/** Import a module. + * This operation will load a modules and store the public module handles in the + * handles_out out parameter. The module name will be used to verify if the + * module was already loaded, in which case it won't be reimported. The name + * will be translated from PascalCase to an entity path (pascal.case) before the + * lookup occurs. + * + * Module contents will be stored as children of the module entity. This + * prevents modules from accidentally defining conflicting identifiers. This is + * enforced by setting the scope before and after loading the module to the + * module entity id. + * + * A more convenient way to import a module is by using the ECS_IMPORT macro. + * + * @param world The world. + * @param module The module to load. + * @param module_name The name of the module to load. + * @param flags An integer that will be passed into the module import action. + * @param handles_out A struct with handles to the module components/systems. + * @param handles_size Size of the handles_out parameter. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name, + void *handles_out, + size_t handles_size); + +/* Import a module from a library. + * Similar to ecs_import, except that this operation will attempt to load the + * module from a dynamic library. + * + * A library may contain multiple modules, which is why both a library name and + * a module name need to be provided. If only a library name is provided, the + * library name will be reused for the module name. + * + * The library will be looked up using a canonical name, which is in the same + * form as a module, like `flecs.components.transform`. To transform this + * identifier to a platform specific library name, the operation relies on the + * module_to_dl callback of the os_api which the application has to override if + * the default does not yield the correct library name. + * + * @param world The world. + * @param library_name The name of the library to load. + * @param module_name The name of the module to load. + * @param flags The flags to pass to the module. + */ +FLECS_API +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name); + +/** Define module + */ +#define ECS_MODULE(world, id)\ + ECS_ENTITY_VAR(id) = ecs_new_module(world, 0, #id, sizeof(id), ECS_ALIGNOF(id));\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + id *handles = (id*)ecs_get_mut(world, ecs_typeid(id), id, NULL);\ + (void)ecs_typeid(id);\ + (void)ecs_type(id);\ + (void)handles; + +/** Wrapper around ecs_import. + * This macro provides a convenient way to load a module with the world. It can + * be used like this: + * + * ECS_IMPORT(world, FlecsSystemsPhysics, 0); + * + * This macro will define entity and type handles for the component associated + * with the module. An application can retrieve the module component like this: + * + * FlecsSystemsPhysics m = ecs_get(world, EcsSingleton, FlecsSystemsPhysics); + * + * The contents of a module component are module specific, although they + * typically contain handles to the content of the module. + */ +#define ECS_IMPORT(world, id) \ + id ecs_module(id);\ + char *id##__name = ecs_module_path_from_c(#id);\ + ECS_ENTITY_VAR(id) = ecs_import(\ + world, id##Import, id##__name, &ecs_module(id), sizeof(id));\ + ecs_os_free(id##__name);\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + id##ImportHandles(ecs_module(id));\ + (void)ecs_typeid(id);\ + (void)ecs_type(id);\ + +/** Declare type variable */ +#define ECS_TYPE_VAR(id)\ + ecs_type_t ecs_type(id) + +/** Declare entity variable */ +#define ECS_ENTITY_VAR(id)\ + ecs_entity_t ecs_typeid(id) + +/** Utility macro for declaring a component inside a handles type */ +#define ECS_DECLARE_COMPONENT(id)\ + ECS_ENTITY_VAR(id);\ + ECS_TYPE_VAR(id) + +/** Utility macro for declaring an entity inside a handles type */ +#define ECS_DECLARE_ENTITY(id)\ + ecs_entity_t id;\ + ECS_TYPE_VAR(id) + +/** Utility macro for declaring a type inside a handles type */ +#define ECS_DECLARE_TYPE(id)\ + ECS_DECLARE_ENTITY(id) + +/** Utility macro for setting a component in a module function */ +#define ECS_SET_COMPONENT(id)\ + if (handles) handles->ecs_typeid(id) = ecs_typeid(id);\ + if (handles) handles->ecs_type(id) = ecs_type(id) + +/** Utility macro for setting an entity in a module function */ +#define ECS_SET_ENTITY(id)\ + if (handles) handles->id = id; + +/** Utility macro for setting a type in a module function */ +#define ECS_SET_TYPE(id)\ + if (handles) handles->id = id;\ + if (handles) handles->ecs_type(id) = ecs_type(id); + +#define ECS_EXPORT_COMPONENT(id)\ + ECS_SET_COMPONENT(id) + +#define ECS_EXPORT_ENTITY(id)\ + ECS_SET_ENTITY(id) + +#define ECS_EXPORT_TYPE(id)\ + ECS_SET_TYPE(id) + +/** Utility macro for importing a component */ +#define ECS_IMPORT_COMPONENT(handles, id)\ + ECS_ENTITY_VAR(id) = (handles).ecs_typeid(id); (void)ecs_typeid(id);\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + (void)ecs_typeid(id);\ + (void)ecs_type(id) + +/** Utility macro for importing an entity */ +#define ECS_IMPORT_ENTITY(handles, id)\ + ecs_entity_t id = (handles).id;\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &id, 1);\ + (void)id;\ + (void)ecs_type(id) + +/** Utility macro for importing a type */ +#define ECS_IMPORT_TYPE(handles, id)\ + ecs_entity_t id = (handles).id;\ + ecs_type_t ecs_type(id) = (handles).ecs_type(id);\ + (void)id;\ + (void)ecs_type(id) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +#ifndef FLECS_SYSTEMS_H +#define FLECS_SYSTEMS_H + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Components +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +extern ecs_type_t + ecs_type(EcsTrigger), + ecs_type(EcsSystem), + ecs_type(EcsTickSource), + ecs_type(EcsSignatureExpr), + ecs_type(EcsSignature), + ecs_type(EcsQuery), + ecs_type(EcsIterAction), + ecs_type(EcsContext); + +/* Component used to provide a tick source to systems */ +typedef struct EcsTickSource { + bool tick; /* True if providing tick */ + FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ +} EcsTickSource; + +/* Signature expression */ +typedef struct EcsSignatureExpr { + const char *expr; +} EcsSignatureExpr; + +/* Parsed signature */ +typedef struct EcsSignature { + ecs_sig_t signature; +} EcsSignature; + +/* Query component */ +typedef struct EcsQuery { + ecs_query_t *query; +} EcsQuery; + +/* System action */ +typedef struct EcsIterAction { + ecs_iter_action_t action; +} EcsIterAction; + +/* System context */ +typedef struct EcsContext { + const void *ctx; +} EcsContext; + + +//////////////////////////////////////////////////////////////////////////////// +//// Systems API +//////////////////////////////////////////////////////////////////////////////// + +/** Declare a systen. + * This macro declares a system with the specified function, kind and signature. + * Systems are matched with entities that match the system signature. The system + * signature is specified as a comma-separated list of column expressions, where + * a column expression can be any of the following: + * + * - A simple component identifier ('Position') + * - An OR expression ('Position | Velocity') + * - An optional expression ('?Position') + * - A NOT expression ('!Position') + * - An OWNED expression ('OWNED:Position') + * - A SHARED expression ('SHARED:Position') + * - A PARENT expression ('PARENT:Position') + * - A CASCADE expression ('CASCADE:Position') + * - An entity expression ('MyEntity:Position') + * - An empty expression (':Position') + * + * The systen kind specifies the phase in which the system is ran. + * + * Examples: + * ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity, !AngularVelocity); + * ECS_SYSTEM(world, Transform, EcsPostUpdate, PARENT:Transform, Transform); + * + * In these examples, 'Move' and 'Transform' must be valid identifiers to a C + * function of the following signature: + * + * void Move(ecs_iter_t *it) { ... } + * + * Inside this function the system can access the data from the signature with + * the ECS_COLUMN macro: + * + * ECS_COLUMN(it, Position, p, 1); + * ECS_COLUMN(it, Velocity, v, 2); + * + * For more details on system signatures and phases see the Flecs manual. + */ + +#ifndef FLECS_LEGACY +#define ECS_SYSTEM(world, name, kind, ...) \ + ecs_iter_action_t ecs_iter_action(name) = name;\ + ecs_entity_t name = ecs_new_system(world, 0, #name, kind, #__VA_ARGS__, ecs_iter_action(name));\ + (void)ecs_iter_action(name);\ + (void)name; +#endif + +#define ECS_TRIGGER(world, name, kind, component) \ + ecs_entity_t __F##name = ecs_new_trigger(world, 0, #name, kind, #component, name);\ + ecs_entity_t name = __F##name;\ + (void)__F##name;\ + (void)name; + +/** Run a specific system manually. + * This operation runs a single system manually. It is an efficient way to + * invoke logic on a set of entities, as manual systems are only matched to + * tables at creation time or after creation time, when a new table is created. + * + * Manual systems are useful to evaluate lists of prematched entities at + * application defined times. Because none of the matching logic is evaluated + * before the system is invoked, manual systems are much more efficient than + * manually obtaining a list of entities and retrieving their components. + * + * An application may pass custom data to a system through the param parameter. + * This data can be accessed by the system through the param member in the + * ecs_iter_t value that is passed to the system callback. + * + * Any system may interrupt execution by setting the interrupted_by member in + * the ecs_iter_t value. This is particularly useful for manual systems, where + * the value of interrupted_by is returned by this operation. This, in + * cominbation with the param argument lets applications use manual systems + * to lookup entities: once the entity has been found its handle is passed to + * interrupted_by, which is then subsequently returned. + * + * @param world The world. + * @param system The system to run. + * @param delta_time: The time passed since the last system invocation. + * @param param A user-defined parameter to pass to the system. + * @return handle to last evaluated entity if system was interrupted. + */ +FLECS_API +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + void *param); + +/** Run system with offset/limit and type filter. + * This operation is the same as ecs_run, but filters the entities that will be + * iterated by the system. + * + * Entities can be filtered in two ways. Offset and limit control the range of + * entities that is iterated over. The range is applied to all entities matched + * with the system, thus may cover multiple archetypes. + * + * The type filter controls which entity types the system will evaluate. Only + * types that contain all components in the type filter will be iterated over. A + * type filter is only evaluated once per table, which makes filtering cheap if + * the number of entities is large and the number of tables is small, but not as + * cheap as filtering in the system signature. + * + * @param world The world. + * @param system The system to invoke. + * @param delta_time: The time passed since the last system invocation. + * @param filter A component or type to filter matched entities. + * @param param A user-defined parameter to pass to the system. + * @return handle to last evaluated entity if system was interrupted. + */ +FLECS_API +ecs_entity_t ecs_run_w_filter( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param); + +/** System status change callback */ +typedef enum ecs_system_status_t { + EcsSystemStatusNone = 0, + EcsSystemEnabled, + EcsSystemDisabled, + EcsSystemActivated, + EcsSystemDeactivated +} ecs_system_status_t; + +typedef void (*ecs_system_status_action_t)( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_status_t status, + void *ctx); + +/** Set system status action. + * The status action is invoked whenever a system is enabled or disabled. Note + * that a system may be enabled but may not actually match any entities. In this + * case the system is enabled but not _active_. + * + * In addition to communicating the enabled / disabled status, the action also + * communicates changes in the activation status of the system. A system becomes + * active when it has one or more matching entities, and becomes inactive when + * it no longer matches any entities. + * + * A system switches between enabled and disabled when an application invokes the + * ecs_enable operation with a state different from the state of the system, for + * example the system is disabled, and ecs_enable is invoked with enabled: true. + * + * Additionally a system may switch between enabled and disabled when it is an + * EcsOnDemand system, and interest is generated or lost for one of its [out] + * columns. + * + * @param world The world. + * @param system The system for which to set the action. + * @param action The action. + * @param ctx Context that will be passed to the action when invoked. + */ +FLECS_API +void ecs_set_system_status_action( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_status_action_t action, + const void *ctx); + + +//////////////////////////////////////////////////////////////////////////////// +//// System debug API +//////////////////////////////////////////////////////////////////////////////// + +typedef struct ecs_dbg_system_t { + ecs_entity_t system; + int32_t entities_matched_count; + int32_t active_table_count; + int32_t inactive_table_count; + bool enabled; + void *system_data; +} ecs_dbg_system_t; + +FLECS_API +int ecs_dbg_system( + ecs_world_t *world, + ecs_entity_t system, + ecs_dbg_system_t *dbg_out); + +FLECS_API +ecs_table_t* ecs_dbg_get_active_table( + ecs_world_t *world, + ecs_dbg_system_t *dbg, + int32_t index); + +FLECS_API +ecs_table_t* ecs_dbg_get_inactive_table( + ecs_world_t *world, + ecs_dbg_system_t *dbg, + int32_t index); + +FLECS_API +ecs_type_t ecs_dbg_get_column_type( + ecs_world_t *world, + ecs_entity_t system, + int32_t column_index); + +FLECS_API +bool ecs_dbg_match_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t system, + ecs_match_failure_t *failure_info_out); + + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/* Pipeline component is empty: components and tags in module are static */ +typedef struct FlecsSystem { + int32_t dummy; +} FlecsSystem; + +FLECS_API +void FlecsSystemImport( + ecs_world_t *world); + +#define FlecsSystemImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_PIPELINE +#ifdef FLECS_PIPELINE +#define FLECS_SYSTEM + + +#ifndef FLECS_PIPELINE_H +#define FLECS_PIPELINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY +#define ECS_PIPELINE(world, name, ...) \ + ecs_entity_t name = ecs_new_pipeline(world, 0, #name, #__VA_ARGS__); +#endif + +/** Set a custom pipeline. + * This operation sets the pipeline to run when ecs_progress is invoked. + * + * @param world The world. + * @param pipeline The pipeline to set. + */ +FLECS_API +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline); + +/** Get the current pipeline. + * This operation gets the current pipeline. + * + * @param world The world. + * @param pipeline The pipeline to set. + */ +FLECS_API +ecs_entity_t ecs_get_pipeline( + ecs_world_t *world); + +/** Progress a world. + * This operation progresses the world by running all systems that are both + * enabled and periodic on their matching entities. + * + * An application can pass a delta_time into the function, which is the time + * passed since the last frame. This value is passed to systems so they can + * update entity values proportional to the elapsed time since their last + * invocation. + * + * When an application passes 0 to delta_time, ecs_progress will automatically + * measure the time passed since the last frame. If an application does not uses + * time management, it should pass a non-zero value for delta_time (1.0 is + * recommended). That way, no time will be wasted measuring the time. + * + * @param world The world to progress. + * @param delta_time The time passed since the last frame. + * @return false if ecs_quit has been called, true otherwise. + */ +FLECS_API +bool ecs_progress( + ecs_world_t *world, + FLECS_FLOAT delta_time); + +/** Set target frames per second (FPS) for application. + * Setting the target FPS ensures that ecs_progress is not invoked faster than + * the specified FPS. When enabled, ecs_progress tracks the time passed since + * the last invocation, and sleeps the remaining time of the frame (if any). + * + * This feature ensures systems are ran at a consistent interval, as well as + * conserving CPU time by not running systems more often than required. + * + * Note that ecs_progress only sleeps if there is time left in the frame. Both + * time spent in flecs as time spent outside of flecs are taken into + * account. + * + * @param world The world. + * @param fps The target FPS. + */ +FLECS_API +void ecs_set_target_fps( + ecs_world_t *world, + FLECS_FLOAT fps); + +/** Set time scale. + * Increase or decrease simulation speed by the provided multiplier. + * + * @param world The world. + * @param scale The scale to apply (default = 1). + */ +FLECS_API +void ecs_set_time_scale( + ecs_world_t *world, + FLECS_FLOAT scale); + +/** Reset world clock. + * Reset the clock that keeps track of the total time passed in the simulation. + * + * @param world The world. + */ +FLECS_API +void ecs_reset_clock( + ecs_world_t *world); + +/** Signal exit + * This operation signals that the application should quit. It will cause + * ecs_progress to return false. + * + * @param world The world to quit. + */ +FLECS_API +void ecs_quit( + ecs_world_t *world); + +/** Deactivate systems that are not matched with tables. + * By default Flecs deactivates systems that are not matched with any tables. + * However, once a system has been matched with a table it remains activated, to + * prevent systems from continuously becoming active and inactive. + * + * To re-deactivate systems, an application can invoke this function, which will + * deactivate all systems that are not matched with any tables. + * + * @param world The world. + */ +FLECS_API +void ecs_deactivate_systems( + ecs_world_t *world); + + +//////////////////////////////////////////////////////////////////////////////// +//// Threading +//////////////////////////////////////////////////////////////////////////////// + +/** Set number of worker threads. + * Setting this value to a value higher than 1 will start as many threads and + * will cause systems to evenly distribute matched entities across threads. The + * operation may be called multiple times to reconfigure the number of threads + * used, but never while running a system / pipeline. */ +FLECS_API +void ecs_set_threads( + ecs_world_t *world, + int32_t threads); + +/** Get current number of threads. */ +FLECS_API +int32_t ecs_get_threads( + ecs_world_t *world); + +/** Get current thread index */ +FLECS_API +int32_t ecs_get_thread_index( + ecs_world_t *world); + + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/* Pipeline component is empty: components and tags in module are static */ +typedef struct FlecsPipeline { + int32_t dummy; +} FlecsPipeline; + +FLECS_API +void FlecsPipelineImport( + ecs_world_t *world); + +#define FlecsPipelineImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_TIMER +#ifdef FLECS_STATS +#define FLECS_MODULE +#define FLECS_PIPELINE + +#ifndef FLECS_TIMER_H +#define FLECS_TIMER_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Components +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +extern ecs_type_t + ecs_type(EcsTimer), + ecs_type(EcsRateFilter); + +/** Component used for timer functionality */ +typedef struct EcsTimer { + FLECS_FLOAT timeout; /* Timer timeout period */ + FLECS_FLOAT time; /* Incrementing time value */ + int32_t fired_count; /* Number of times ticked */ + bool active; /* Is the timer active or not */ + bool single_shot; /* Is this a single shot timer */ +} EcsTimer; + +/* Apply a rate filter to a tick source */ +typedef struct EcsRateFilter { + ecs_entity_t src; + int32_t rate; + int32_t tick_count; + FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ +} EcsRateFilter; + + +//////////////////////////////////////////////////////////////////////////////// +//// Timer API +//////////////////////////////////////////////////////////////////////////////// + +/** Set timer timeout. + * This operation executes any systems associated with the timer after the + * specified timeout value. If the entity contains an existing timer, the + * timeout value will be reset. + * + * Any entity can be used as a timer (including systems). If a timeout value is + * set on a system entity, it will be automatically applied to that system. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * @param world The world. + * @param timer The timer for which to set the timeout (0 to create one). + * @param timeout The timeout value. + * @return The timer entity. + */ +FLECS_API +ecs_entity_t ecs_set_timeout( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT timeout); + +/** Get current timeout value for the specified timer. + * This operation returns the value set by ecs_set_timeout. If no timer is + * active for this entity, the operation returns 0. + * + * After the timeout expires the timer component is removed from the entity. + * This means that if ecs_get_timeout is invoked after the timer is expired, the + * operation will return 0. + * + * @param world The world. + * @param timer The timer. + * @return The current timeout value, or 0 if no timer is active. + */ +FLECS_API +FLECS_FLOAT ecs_get_timeout( + ecs_world_t *world, + ecs_entity_t timer); + +/** Set timer interval. + * This operation will continously invoke systems associated with the timer + * after the interval period expires. If the entity contains an existing timer, + * the interval value will be reset. + * + * Any entity can be used as a timer (including systems). If an interval value + * is set on a system entity, it will be automatically applied to that system. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * @param world The world. + * @param timer The timer for which to set the interval (0 to create one). + * @param interval The interval value. + * @return The timer entity. + */ +FLECS_API +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT interval); + +/** Get current interval value for the specified timer. + * This operation returns the value set by ecs_set_interval. If no timer is + * active for this entity, the operation returns 0. + * + * @param world The world. + * @param timer The timer for which to set the interval. If 0, an entity will be created. + * @return The current interval value, or 0 if no timer is active. + */ +FLECS_API +FLECS_FLOAT ecs_get_interval( + ecs_world_t *world, + ecs_entity_t timer); + +/** Start timer. + * This operation resets the timer and starts it with the specified timeout. The + * entity must have the EcsTimer component (added by ecs_set_timeout and + * ecs_set_interval). If the entity does not have the EcsTimer component this + * operation will assert. + * + * @param world The world. + * @param timer The timer to start. + */ +FLECS_API +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t timer); + +/** Stop timer + * This operation stops a timer from triggering. The entity must have the + * EcsTimer component or this operation will assert. + * + * @param world The world. + * @param timer The timer to stop. + */ +FLECS_API +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t timer); + +/** Set rate filter. + * This operation sets the source and rate for a rate filter. A rate filter + * samples another tick source (or frames, if none provided) and ticks when the + * number of sampled ticks equals the rate. + * + * @param world The world. + * @param filter The filter entity (0 to create one). + * @param rate The rate to apply. + * @param source The tick source (0 to use frames) + * @return The filter entity. + */ +FLECS_API +ecs_entity_t ecs_set_rate_filter( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source); + +/** Assign tick source to system. + * This operation associates a system with a tick source. If the system is both + * active and enabled at the moment the tick source fires, it will be executed. + * If no tick source is associated with a system, it will be invoked every + * frame. + * + * To disassociate a tick source from a system, use 0 for the tick_source + * parameter. + * + * Timer and rate filter entities are valid tick sources. An application can + * also create its own tick source by setting the EcsTickSource component on an + * entity. + * + * If an entity without the EcsTickSource component is provided as tick source, + * the system will not be executed. + * + * @param world The world. + * @param system The system to associate with the timer. + * @param timer The timer to associate with the system. + */ +FLECS_API +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source); + + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/* Timers module component */ +typedef struct FlecsTimer { + int32_t dummy; +} FlecsTimer; + +FLECS_API +void FlecsTimerImport( + ecs_world_t *world); + +#define FlecsTimerImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif + +/* Optional addons */ +#ifdef FLECS_BULK +#ifdef FLECS_BULK + +/** + * @file bulk.h + * @brief Bulk API. + */ + +#ifndef FLECS_BULK_H +#define FLECS_BULK_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Add an entity to entities matching a filter. + * This operation is the same as ecs_add_entity, but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param entity_add The entity to add. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_entity( + ecs_world_t *world, + ecs_entity_t entity_add, + const ecs_filter_t *filter); + +/** Add a type to entities matching a filter. + * This operation is the same as ecs_add_type but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The type to add. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_type( + ecs_world_t *world, + ecs_type_t type, + const ecs_filter_t *filter); + +/** Add a component / type / tag to entities matching a filter. + * This operation is the same as ecs_add but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The component, type or tag to add. + * @param filter The filter. + */ +#define ecs_bulk_add(world, type, filter)\ + ecs_bulk_add_type(world, ecs_type(type), filter) + +/** Removes an entity from entities matching a filter. + * This operation is the same as ecs_remove_entity, but is applied to all + * entities that match the provided filter. + * + * @param world The world. + * @param entity_remove The entity to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_remove_entity( + ecs_world_t *world, + ecs_entity_t entity_remove, + const ecs_filter_t *filter); + +/** Remove a type from entities matching a filter. + * This operation is the same as ecs_remove_type but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The type to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_remove_type( + ecs_world_t *world, + ecs_type_t type, + const ecs_filter_t *filter); + +/** Add a component / type / tag to entities matching a filter. + * This operation is the same as ecs_remove but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The component, type or tag to remove. + * @param filter The filter. + */ +#define ecs_bulk_remove(world, type, filter)\ + ecs_bulk_remove_type(world, ecs_type(type), filter) + +/** Add / remove type from entities matching a filter. + * Combination of ecs_bulk_add_type and ecs_bulk_remove_type. + * + * @param world The world. + * @param to_add The type to add. + * @param to_remove The type to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_remove_type( + ecs_world_t *world, + ecs_type_t to_add, + ecs_type_t to_remove, + const ecs_filter_t *filter); + +/** Add / remove component, type or tag from entities matching a filter. + * Combination of ecs_bulk_add and ecs_bulk_remove. + * + * @param world The world. + * @param to_add The component, type or tag to add. + * @param to_remove The component, type or tag to remove. + * @param filter The filter. + */ +#define ecs_bulk_add_remove(world, to_add, to_remove, filter)\ + ecs_bulk_add_remove_type(world, ecs_type(to_add), ecs_type(to_remove), filter) + +/** Delete entities matching a filter. + * This operation is the same as ecs_delete, but applies to all entities that + * match a filter. + * + * @param world The world. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_DBG +#ifdef FLECS_DBG + +#ifndef FLECS_DBG_H +#define FLECS_DBG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Unstable API */ + +typedef struct ecs_dbg_entity_t { + ecs_entity_t entity; + ecs_table_t *table; + ecs_type_t type; + int32_t row; + bool is_watched; +} ecs_dbg_entity_t; + +typedef struct ecs_dbg_table_t { + ecs_table_t *table; + ecs_type_t type; + ecs_type_t shared; + ecs_type_t container; + ecs_type_t parent_entities; + ecs_type_t base_entities; + ecs_vector_t *systems_matched; + ecs_entity_t *entities; + int32_t entities_count; +} ecs_dbg_table_t; + +FLECS_API +void ecs_dbg_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_dbg_entity_t *dbg_out); + +FLECS_API +ecs_table_t *ecs_dbg_find_table( + ecs_world_t *world, + ecs_type_t type); + +FLECS_API +ecs_table_t *ecs_dbg_get_table( + ecs_world_t *world, + int32_t index); + +FLECS_API +bool ecs_dbg_filter_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_filter_t *filter); + +FLECS_API +void ecs_dbg_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_dbg_table_t *dbg_out); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_MODULE +#endif +#ifdef FLECS_QUEUE +#ifdef FLECS_QUEUE + +#ifndef FLECS_QUEUE_H_ +#define FLECS_QUEUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_queue_t ecs_queue_t; + +FLECS_API +ecs_queue_t* _ecs_queue_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_queue_new(T, elem_count)\ + _ecs_queue_new(ECS_VECTOR_T(T), elem_count) + +FLECS_API +ecs_queue_t* _ecs_queue_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array); + +#define ecs_queue_from_array(T, elem_count, array)\ + _ecs_queue_from_array(ECS_VECTOR_T(T), elem_count, array) + +FLECS_API +void* _ecs_queue_push( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_queue_push(queue, T)\ + (T*)_ecs_queue_push(queue, ECS_VECTOR_T(T)) + +FLECS_API +void* _ecs_queue_get( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_queue_get(queue, T, index)\ + (T*)_ecs_queue_get(queue, ECS_VECTOR_T(T), index) + +#define ecs_queue_get_t(vector, size, alignment, index) \ + _ecs_queue_get(vector, ECS_VECTOR_U(size, alignment), index) + +FLECS_API +void* _ecs_queue_last( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_queue_last(queue, T)\ + (T*)_ecs_queue_last(queue, ECS_VECTOR_T(T)) + +FLECS_API +int32_t ecs_queue_index( + ecs_queue_t *queue); + +FLECS_API +int32_t ecs_queue_count( + ecs_queue_t *queue); + +FLECS_API +void ecs_queue_free( + ecs_queue_t *queue); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_READER_WRITER +#ifdef FLECS_READER_WRITER + +/** + * @file serializer.h + * @brief Blob serializer API. + */ + +#ifndef FLECS_READER_WRITER_H +#define FLECS_READER_WRITER_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum ecs_blob_header_kind_t { + EcsStreamHeader, + + /* Stream states */ + EcsTableSegment, + EcsFooterSegment, + + /* Table segment */ + EcsTableHeader, + EcsTableTypeSize, + EcsTableType, + EcsTableSize, + EcsTableColumn, + EcsTableColumnHeader, + EcsTableColumnSize, + EcsTableColumnData, + + /* Name column (EcsName) */ + EcsTableColumnNameHeader, + EcsTableColumnNameLength, + EcsTableColumnName, + + EcsStreamFooter +} ecs_blob_header_kind_t; + +typedef struct ecs_table_reader_t { + ecs_blob_header_kind_t state; + + int32_t table_index; + ecs_table_t *table; + ecs_data_t *data; + + /* Current index in type */ + ecs_size_t type_written; + ecs_type_t type; + + /* Current column */ + ecs_vector_t *column_vector; + int32_t column_index; + int32_t total_columns; + + /* Keep track of how much of the component column has been written */ + void *column_data; + int16_t column_size; + int16_t column_alignment; + ecs_size_t column_written; + + /* Keep track of row when writing non-blittable data */ + int32_t row_index; + int32_t row_count; + + /* Keep track of how much of an entity name has been written */ + const char *name; + ecs_size_t name_len; + ecs_size_t name_written; + + bool has_next_table; +} ecs_table_reader_t; + +typedef struct ecs_reader_t { + ecs_world_t *world; + ecs_blob_header_kind_t state; + ecs_iter_t data_iter; + ecs_iter_next_action_t data_next; + ecs_iter_t component_iter; + ecs_iter_next_action_t component_next; + ecs_table_reader_t table; +} ecs_reader_t; + +typedef struct ecs_name_writer_t { + char *name; + int32_t written; + int32_t len; + int32_t max_len; +} ecs_name_writer_t; + +typedef struct ecs_table_writer_t { + ecs_blob_header_kind_t state; + + ecs_table_t *table; + ecs_vector_t *column_vector; + + /* Keep state for parsing type */ + int32_t type_count; + int32_t type_max_count; + ecs_size_t type_written; + ecs_entity_t *type_array; + + int32_t column_index; + int16_t column_size; + int16_t column_alignment; + ecs_size_t column_written; + void *column_data; + + int32_t row_count; + int32_t row_index; + ecs_name_writer_t name; +} ecs_table_writer_t; + +typedef struct ecs_writer_t { + ecs_world_t *world; + ecs_blob_header_kind_t state; + ecs_table_writer_t table; + int error; +} ecs_writer_t; + +/** Initialize a reader. + * A reader serializes data in a world to a sequence of bytes that can be stored + * in a file or sent across a network. + * + * @param world The world to serialize. + * @return The reader. + */ +FLECS_API +ecs_reader_t ecs_reader_init( + ecs_world_t *world); + +/** Initialize a snapshot reader. + * A snapshot reader serializes data in a snapshot to a sequence of bytes that + * can be stored in a file or sent across a network. A snapshot reader has as + * advantage that serialization can take place asynchronously while the world + * is progressing. + * + * @param world The world in which the snapshot is taken. + * @param iter Iterator to the data to be serialized. + * @return The reader. + */ +FLECS_API +ecs_reader_t ecs_reader_init_w_iter( + ecs_iter_t *iter, + ecs_iter_next_action_t next); + +/** Read from a reader. + * This operation reads a specified number of bytes from a reader and stores it + * in the specified buffer. When there are no more bytes to read from the reader + * the operation will return 0, otherwise it will return the number of bytes + * read. + * + * The specified buffer must be at least as big as the specified size, and the + * specified size must be a multiple of 4. + * + * @param buffer The buffer in which to store the read bytes. + * @param size The maximum number of bytes to read. + * @param reader The reader from which to read the bytes. + * @return The number of bytes read. + */ +FLECS_API +int32_t ecs_reader_read( + char *buffer, + int32_t size, + ecs_reader_t *reader); + +/** Initialize a writer. + * A writer deserializes data from a sequence of bytes into a world. This + * enables applications to restore data from disk or the network. + * + * The provided world must be either empty or compatible with the data to + * deserialize, where compatible means that the serialized component ids and + * sizes must match exactly with those in the world. Errors can occur if a world + * is provided in which components have been declared in a different order, or + * when components have different type definitions. + * + * @param world The world in which to deserialize the data. + * @return The writer. + */ +FLECS_API +ecs_writer_t ecs_writer_init( + ecs_world_t *world); + +/** Write to a writer. + * This operation writes a specified number of bytes from a specified buffer + * into the writer. The writer will restore the deserialized data into the + * original serialized entities. The write operation may be invoked multiple + * times with partial buffers, which allows applications to use static buffers + * when reading from, for example, a file or the network. + * + * The data contained in the buffers must have been serialized with the + * ecs_reader_read operation. If the data does not match the expected format, or + * the data contains conflicts with the world, the operation will fail. The + * data must be provided in the same order as produced by ecs_reader_read, + * but the used buffer size does not have to be the same as the one used by + * ecs_reader_read. The buffer size must be a multiple of 4. + * + * @param buffer The buffer to deserialize. + * @param size The maximum number of bytes. + * @param writer The writer to write to. + * @return Zero if success, non-zero if failed to deserialize. + */ +FLECS_API +int32_t ecs_writer_write( + const char *buffer, + int32_t size, + ecs_writer_t *writer); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_SNAPSHOT +#ifdef FLECS_SNAPSHOT + +/** + * @file snapshot.h + * @brief Snapshot API. + */ + +#ifndef FLECS_SNAPSHOT_H +#define FLECS_SNAPSHOT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Create a snapshot. + * This operation makes a copy of all component in the world that matches the + * specified filter. + * + * @param world The world to snapshot. + * @param return The snapshot. + */ +FLECS_API +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *world); + +/** Create a filtered snapshot. + * This operation is the same as ecs_snapshot_take, but accepts an iterator so + * an application can control what is stored by the snapshot. + * + * @param iter An iterator to the data to be stored by the snapshot. + * @param next A function pointer to the next operation for the iterator. + * @param return The snapshot. + */ +FLECS_API +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter, + ecs_iter_next_action_t action); + +/** Restore a snapshot. + * This operation restores the world to the state it was in when the specified + * snapshot was taken. A snapshot can only be used once for restoring, as its + * data replaces the data that is currently in the world. + * This operation also resets the last issued entity handle, so any calls to + * ecs_new may return entity ids that have been issued before restoring the + * snapshot. + * + * The world in which the snapshot is restored must be the same as the world in + * which the snapshot is taken. + * + * @param world The world to restore the snapshot to. + * @param snapshot The snapshot to restore. + */ +FLECS_API +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot); + +/** Obtain iterator to snapshot data. + * + * @param snapshot The snapshot to iterate over. + * @return Iterator to snapshot data. */ +FLECS_API +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot, + const ecs_filter_t *filter); + +/** Progress snapshot iterator. + * + * @param iter The snapshot iterator. + * @return True if more data is available, otherwise false. + */ +FLECS_API +bool ecs_snapshot_next( + ecs_iter_t *iter); + + +/** Free snapshot resources. + * This frees resources associated with a snapshot without restoring it. + * + * @param world The world. + * @param snapshot The snapshot to free. + */ +FLECS_API +void ecs_snapshot_free( + ecs_snapshot_t *snapshot); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif + +/** Direct access API. + * + * This API allows for low-level direct access to tables and their columns. The + * APIs primary intent is to provide fast primitives for new operations. It is + * not recommended to use the API directly in application code, as invoking the + * API in an incorrect way can lead to a corrupted datastore. + */ + +#ifndef FLECS_DIRECT_ACCESS_H_ +#define FLECS_DIRECT_ACCESS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* These functions are part of the core and available even when the direct + * access API is not part of a custom build. */ + +/** Find or create table with specified component string. + * The provided string must be a comma-separated list of fully qualified + * component identifiers. The returned table will have the specified components. + * Two lists that are the same but specify components in a different order will + * return the same table. + * + * @param world The world. + * @param type The components. + * @return The new or existing table, or NULL if the string contains an error. + */ +FLECS_API +ecs_table_t* ecs_table_from_str( + ecs_world_t *world, + const char *type); + +/** Find or create table from type. + * Same as ecs_table_from_str, but provides the type directly. + * + * @param world The world. + * @param type The type. + * @return The new or existing table. + */ +FLECS_API +ecs_table_t* ecs_table_from_type( + ecs_world_t *world, + ecs_type_t type); + +/** Get type for table. + * + * @param table The table. + * @return The type of the table. + */ +FLECS_API +ecs_type_t ecs_table_get_type( + ecs_table_t *table); + +/** Insert record into table. + * This will create a new record for the table, which inserts a value for each + * component. An optional entity and record can be provided. + * + * If a non-zero entity id is provided, a record must also be provided and vice + * versa. The record must be created by the entity index. If the provided record + * is not created for the specified entity, the behavior will be undefined. + * + * If the provided record is not managed by the entity index, the behavior will + * be undefined. + * + * The returned record contains a reference to the table and the table row. The + * data pointed to by the record is guaranteed not to move unless one or more + * rows are removed from this table. A row can be removed as result of a delete, + * or by adding/removing components from an entity stored in the table. + * + * @param world The world. + * @param table The table. + * @param entity The entity. + * @param record The entity-index record for the specified entity. + * @return A record containing the table and table row. + */ +FLECS_API +ecs_record_t ecs_table_insert( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record); + +/** Returns the number of records in the table. + * This operation returns the number of records that have been populated through + * the regular (entity) API as well as the number of records that have been + * inserted using the direct access API. + * + * @param world The world. + * @param table The table. + * @return The number of records in a table. + */ +FLECS_API +int32_t ecs_table_count( + ecs_table_t *table); + +/* From here on functions are only available when the direct access API is part + * of a (custom) build. */ + +#ifdef FLECS_DIRECT_ACCESS + +/** Find the index of a column in a table. + * Table columns are stored in the order of their respective component ids. As + * this is not trivial for an application to deduce, this operation returns the + * index of a column in a table for a given component. This index can be used + * in other table operations to identify a column. + * + * The returned index is determined separately for each table. Indices obtained + * for one table should not be used for another table. + * + * @param table The table. + * @param component The component for which to retrieve the column index. + * @return The column index, or -1 if the table does not have the component. + */ +FLECS_API +int32_t ecs_table_find_column( + ecs_table_t *table, + ecs_entity_t component); + +/** Get table column. + * This operation returns the pointer to a column array. A column contains all + * the data for a component for the provided table in a contiguous array. + * + * The returned pointer is not stable, and may change when a table needs to + * resize its arrays, for example in order to accomodate for more records. + * + * @param table The table. + * @param column The column index. + * @return Vector that contains the column array. + */ +FLECS_API +ecs_vector_t* ecs_table_get_column( + ecs_table_t *table, + int32_t column); + +/** Set table column. + * This operation enables an application to set a component column for a table. + * After the operation the column is owned by the table. Any operations that + * change the column after this operation can cause undefined behavior. + * + * Care must be taken that all columns in a table have the same number of + * elements. If one column has less elements than another, the behavior is + * undefined. The operation will not check if the assigned column is of the same + * size as other columns, as this would prevent an application from assigning + * a set of different columns to a table of a different size. + * + * Setting a column will not delete the previous column. It is the + * responsibility of the application to ensure that the old column is deleted + * properly (using ecs_table_delete_column). + * + * The provided vector must have the same element size and alignment as the + * target column. If the size and/or alignment do not match, the behavior will + * be undefined. In debug mode the operation may assert. + * + * If the provided vector is NULL, the table will ensure that a vector is + * created for the provided column. If a vector exists that is not of the + * same size as the entities vector, it will be resized to match. + * + * @param world The world. + * @param table The table. + * @param column The column index. + * @param vector The column data to assing. + */ +FLECS_API +ecs_vector_t* ecs_table_set_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector); + +/** Get the vector containing entity ids for the table. + * This operation obtains the vector with entity ids for the current table. Each + * entity id is associated with one record, and ids are stored in the same order + * as the table records. The element type of the vector is ecs_entity_t. + * + * @param table The table. + * @return The vector containing the table's entities. + */ +FLECS_API +ecs_vector_t* ecs_table_get_entities( + ecs_table_t *table); + +/** Get the vector containing pointers to entity records. + * A table stores cached pointers to entity records for fast access. This + * operation provides direct access to the vector. The element type of the + * vector is ecs_record_t*. + * + * @param table The table. + * @return The vector containing the entity records. + */ +FLECS_API +ecs_vector_t* ecs_table_get_records( + ecs_table_t *table); + +/** Clear records. + * This operation clears records for a world so that they no longer point to a + * table. This is useful to ensure that a world is left in a consistent state + * after moving data to destination world. + * + * @param records The vector with record pointers + */ +FLECS_API +void ecs_records_clear( + ecs_vector_t *records); + +/** Initialize records. + * This operation ensures entity records are updated to the provided table. + * + * @param world The world. + * @param entities The vector with entity identifiers. + * @param records The vector with record pointers. + * @param table The table in which the entities are stored. + */ +FLECS_API +void ecs_records_update( + ecs_world_t *world, + ecs_vector_t *entities, + ecs_vector_t *records, + ecs_table_t *table); + +/** Set the vector containing entity ids for the table. + * This operation sets the vector with entity ids for a table. In addition the + * operation also requires setting a vector with pointers to records. The + * record pointers in the vector need to be managed by the entity index. If they + * are not, this can cause undefined behavior. + * + * The provided vectors must have the same number of elements as the number of + * records in the table. If the element count is not the same, this causes + * undefined behavior. + * + * A table must have an entity and record vector, even if the table does not + * contain entities. For each record that is not an entity, the entity vector + * should contain 0, and the record vector should contain NULL. + * + * @param table The table. + * @param entities The entity vector. + * @param records The record vector. + */ +FLECS_API +void ecs_table_set_entities( + ecs_table_t *table, + ecs_vector_t *entities, + ecs_vector_t *records); + +/** Delete a column. + * This operation frees the memory of a table column and will invoke the + * component destructor if registered. + * + * The provided vector does not need to be the same as the vector in the table. + * The reason the table must be provided is so that the operation can retrieve + * the correct destructor for the component. If the component does not have a + * destructor, an application can alternatively delete the vector directly. + * + * If the specified vector is NULL, the column of the table will be removed and + * the table will be updated to no longer point at the column. If an explicit + * column is provided, the table is not modified. If a column is deleted that is + * still being pointed to by a table, behavior is undefined. It is the + * responsibility of the application to ensure that a table no longer points to + * a deleted column, by using ecs_table_set_column. + * + * Simultaneously, if this operation is used to delete a table column, the + * application should make sure that if the table contains other columns, they + * are either also deleted, or that the deleted column is replaced by a column + * of the same size. Note that this also goes for the entity and record vectors, + * they should have the same number of elements as the other columns. + * + * The vector must be of the same component as the specified column. If the + * vector is not of the same component, behavior will be undefined. In debug + * mode the API may assert, though it may not always be able to detect a + * mismatching vector/column. + * + * After this operation the vector should no longer be used by the application. + * + * @param table The table. + * @param column The column index. + * @param vector The column vector to delete. + */ +FLECS_API +void ecs_table_delete_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector); + +/** Find a record for a given entity. + * This operation finds an existing record in the entity index for a given + * entity. The returned pointer is stable for the lifecycle of the world and can + * be used as argument for the ecs_record_update operation. + * + * The returned record (if found) points to the adminstration that relates an + * entity id to a table. Updating the value of the returned record will cause + * operations like ecs_get and ecs_has to look in the updated table. + * + * Updating this record to a table in which the entity is not stored causes + * undefined behavior. + * + * When the entity has never been created or is not alive this operation will + * return NULL. + * + * @param world The world. + * @param entity The entity. + * @return The record that belongs to the entity, or NULL if not found. + */ +FLECS_API +ecs_record_t* ecs_record_find( + ecs_world_t *world, + ecs_entity_t entity); + +/** Same as ecs_record_find, but creates record if it doesn't exist. + * If an entity id has not been created with ecs_new_*, this function can be + * used to ensure that a record exists for an entity id. If the provided id + * already exists in the world, the operation will return the existing record. + * + * @param world The world. + * @param entity The entity for which to retrieve the record. + * @return The (new or existing) record that belongs to the entity. + */ +FLECS_API +ecs_record_t* ecs_record_ensure( + ecs_world_t *world, + ecs_entity_t entity); + +/** Get value from record. + * This operation gets a component value from a record. The provided column + * index must match the table of the record. + * + * @param r The record. + * @param column The column index of the component to get. + */ +FLECS_API +void* ecs_record_get_column( + ecs_record_t *r, + int32_t column, + size_t size); + +/** Copy value to a component for a record. + * This operation sets the component value of a single component for a record. + * If the component type has a copy action it will be used, otherwise the value + * be memcpyd into the component array. + * + * The provided record does not need to be managed by the entity index but does + * need to point to a valid record in the table. If the provided index is + * outside of the range indicating the number of records in the table, behavior + * is undefined. In debug mode it will cause the operation to assert. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to copy. + */ +FLECS_API +void ecs_record_copy_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + const void *value, + int32_t count); + +/** Memcpy value to a component for a record. + * Same as ecs_record_copy_to, except that this operation will always use + * memcpy. This operation should only be used for components that can be safely + * memcpyd. If the operation is used for a component that has a copy or move + * action, the behavior is undefined. In debug mode the operation may assert. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to move. + */ +FLECS_API +void ecs_record_copy_pod_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + const void *value, + int32_t count); + +/** Move value to a component for a record. + * Same as ecs_record_copy_to, except that it uses the move action. If the + * component has no move action the value will be memcpyd into the component + * array. After this operation the application can no longer assume that the + * value passed into the function is valid. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to move. + */ +FLECS_API +void ecs_record_move_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + void *value, + int32_t count); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif +#ifdef FLECS_STATS +#ifdef FLECS_STATS + +#define FLECS_SYSTEM + + +#ifndef FLECS_STATS_H +#define FLECS_STATS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_STAT_WINDOW (60) + +/** Simple value that indicates current state */ +typedef struct ecs_gauge_t { + float avg[ECS_STAT_WINDOW]; + float min[ECS_STAT_WINDOW]; + float max[ECS_STAT_WINDOW]; +} ecs_gauge_t; + +/* Monotonically increasing counter */ +typedef struct ecs_counter_t { + ecs_gauge_t rate; /**< Keep track of deltas too */ + float value[ECS_STAT_WINDOW]; +} ecs_counter_t; + +typedef struct ecs_world_stats_t { + /* Allows struct to be initialized with {0} */ + int32_t dummy_; + + ecs_gauge_t entity_count; /**< Number of entities */ + ecs_gauge_t component_count; /**< Number of components */ + ecs_gauge_t query_count; /**< Number of queries */ + ecs_gauge_t system_count; /**< Number of systems */ + ecs_gauge_t table_count; /**< Number of tables */ + ecs_gauge_t empty_table_count; /**< Number of empty tables */ + ecs_gauge_t singleton_table_count; /**< Number of singleton tables. Singleton tables are tables with just a single entity that contains itself */ + ecs_gauge_t matched_entity_count; /**< Number of entities matched by queries */ + ecs_gauge_t matched_table_count; /**< Number of tables matched by queries */ + + /* Deferred operations */ + ecs_counter_t new_count; + ecs_counter_t bulk_new_count; + ecs_counter_t delete_count; + ecs_counter_t clear_count; + ecs_counter_t add_count; + ecs_counter_t remove_count; + ecs_counter_t set_count; + ecs_counter_t discard_count; + + /* Timing */ + ecs_counter_t world_time_total_raw; /**< Actual time passed since simulation start (first time progress() is called) */ + ecs_counter_t world_time_total; /**< Simulation time passed since simulation start. Takes into account time scaling */ + ecs_counter_t frame_time_total; /**< Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_counter_t system_time_total; /**< Time spent on processing systems. */ + ecs_counter_t merge_time_total; /**< Time spent on merging deferred actions. */ + ecs_gauge_t fps; /**< Frames per second. */ + ecs_gauge_t delta_time; /**< Delta_time. */ + + /* Frame data */ + ecs_counter_t frame_count_total; /**< Number of frames processed. */ + ecs_counter_t merge_count_total; /**< Number of merges executed. */ + ecs_counter_t pipeline_build_count_total; /**< Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_counter_t systems_ran_frame; /**< Number of systems ran in the last frame. */ + + /** Current position in ringbuffer */ + int32_t t; +} ecs_world_stats_t; + +/* Statistics for a single query (use ecs_get_query_stats) */ +typedef struct ecs_query_stats_t { + ecs_gauge_t matched_table_count; /**< Number of matched non-empty tables. This is the number of tables + * iterated over when evaluating a query. */ + + ecs_gauge_t matched_empty_table_count; /**< Number of matched empty tables. Empty tables are not iterated over when + * evaluating a query. */ + + ecs_gauge_t matched_entity_count; /**< Number of matched entities across all tables */ + + /** Current position in ringbuffer */ + int32_t t; +} ecs_query_stats_t; + +/** Statistics for a single system (use ecs_get_system_stats) */ +typedef struct ecs_system_stats_t { + ecs_query_stats_t query_stats; + ecs_counter_t time_spent; /**< Time spent processing a system */ + ecs_counter_t invoke_count; /**< Number of times system is invoked */ + ecs_gauge_t active; /**< Whether system is active (is matched with >0 entities) */ + ecs_gauge_t enabled; /**< Whether system is enabled */ +} ecs_system_stats_t; + +/** Statistics for all systems in a pipeline. */ +typedef struct ecs_pipeline_stats_t { + /** Vector with system ids of all systems in the pipeline. The systems are + * stored in the order they are executed. Merges are represented by a 0. */ + ecs_vector_t *systems; + + /** Map with system statistics. For each system in the systems vector, an + * entry in the map exists of type ecs_system_stats_t. */ + ecs_map_t *system_stats; +} ecs_pipeline_stats_t; + +/** Get world statistics. + * Obtain statistics for the provided world. This operation loops several times + * over the tables in the world, and can impact application performance. + * + * @param world The world. + * @param stats Out parameter for statistics. + */ +FLECS_API void ecs_get_world_stats( + ecs_world_t *world, + ecs_world_stats_t *stats); + +/** Print world statistics. + * Print statistics obtained by ecs_get_world_statistics and in the + * ecs_world_info_t struct. + * + * @param world The world. + * @param stats The statistics to print. + */ +FLECS_API void ecs_dump_world_stats( + ecs_world_t *world, + const ecs_world_stats_t *stats); + +/** Get query statistics. + * Obtain statistics for the provided query. + * + * @param world The world. + * @param query The query. + * @param stats Out parameter for statistics. + */ +FLECS_API void ecs_get_query_stats( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_stats_t *s); + +/** Get system statistics. + * Obtain statistics for the provided system. + * + * @param world The world. + * @param system The system. + * @param stats Out parameter for statistics. + * @return true if success, false if not a system. + */ +FLECS_API bool ecs_get_system_stats( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *stats); + +/** Get pipeline statistics. + * Obtain statistics for the provided pipeline. + * + * @param world The world. + * @param pipeline The pipeline. + * @param stats Out parameter for statistics. + * @return true if success, false if not a pipeline. + */ +FLECS_API bool ecs_get_pipeline_stats( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *stats); + +/** Measure frame time. + * Frame time measurements measure the total time passed in a single frame, and + * how much of that time was spent on systems and on merging. + * + * Frame time measurements add a small constant-time overhead to an application. + * When an application sets a target FPS, frame time measurements are enabled by + * default. + * + * @param world The world. + * @param enable Whether to enable or disable frame time measuring. + */ +FLECS_API void ecs_measure_frame_time( + ecs_world_t *world, + bool enable); + +/** Measure system time. + * System time measurements measure the time spent in each system. + * + * System time measurements add overhead to every system invocation and + * therefore have a small but measurable impact on application performance. + * System time measurements must be enabled before obtaining system statistics. + * + * @param world The world. + * @param enable Whether to enable or disable system time measuring. + */ +FLECS_API void ecs_measure_system_time( + ecs_world_t *world, + bool enable); + +FLECS_API void ecs_gauge_reduce( + ecs_gauge_t *dst, + int32_t t_dst, + ecs_gauge_t *src, + int32_t t_src); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif + +#ifdef __cplusplus +} + +#ifndef FLECS_NO_CPP +#ifndef FLECS_LEGACY +#pragma once + +/* Unstable API */ + +#include +#include +#include +#include + +namespace flecs { + +//////////////////////////////////////////////////////////////////////////////// +//// Forward declarations and types +//////////////////////////////////////////////////////////////////////////////// + +using world_t = ecs_world_t; +using entity_t = ecs_entity_t; +using type_t = ecs_type_t; +using snapshot_t = ecs_snapshot_t; +using filter_t = ecs_filter_t; +using query_t = ecs_query_t; + +class world; +class snapshot; +class entity; +class type; +class iter; +class filter; +class filter_iterator; +class child_iterator; +class world_filter; +class snapshot_filter; + +template +class query_iterator; + +template +class query; + +template +class system; + +enum match_kind { + MatchAll = EcsMatchAll, + MatchAny = EcsMatchAny, + MatchExact = EcsMatchExact +}; + +namespace _ +{ +template +class component_info; + +template +bool pack_args_to_string( + world_t *world, + std::stringstream& str, + bool is_each = false); +} + +//////////////////////////////////////////////////////////////////////////////// +//// Builtin components and tags +//////////////////////////////////////////////////////////////////////////////// + +/* Builtin components */ +using Component = EcsComponent; +using ComponentLifecycle = EcsComponentLifecycle; +using Trigger = EcsTrigger; +using Type = EcsType; +using Name = EcsName; +using Timer = EcsTimer; +using RateFilter = EcsRateFilter; +using TickSource = EcsTickSource; +using SignatureExpr = EcsSignatureExpr; +using Signature = EcsSignature; +using Query = EcsQuery; +using ViewAction = EcsIterAction; +using Context = EcsContext; + +/* Builtin tag ids */ +static const ecs_entity_t Module = EcsModule; +static const ecs_entity_t Prefab = EcsPrefab; +static const ecs_entity_t Hidden = EcsHidden; +static const ecs_entity_t Disabled = EcsDisabled; +static const ecs_entity_t DisabledIntern = EcsDisabledIntern; +static const ecs_entity_t Inactive = EcsInactive; +static const ecs_entity_t OnDemand = EcsOnDemand; +static const ecs_entity_t Monitor = EcsMonitor; +static const ecs_entity_t Pipeline = EcsPipeline; + +/* Trigger tags */ +static const ecs_entity_t OnAdd = EcsOnAdd; +static const ecs_entity_t OnRemove = EcsOnRemove; +static const ecs_entity_t OnSet = EcsOnSet; + +/* Builtin pipeline tags */ +static const ecs_entity_t PreFrame = EcsPreFrame; +static const ecs_entity_t OnLoad = EcsOnLoad; +static const ecs_entity_t PostLoad = EcsPostLoad; +static const ecs_entity_t PreUpdate = EcsPreUpdate; +static const ecs_entity_t OnUpdate = EcsOnUpdate; +static const ecs_entity_t OnValidate = EcsOnValidate; +static const ecs_entity_t PostUpdate = EcsPostUpdate; +static const ecs_entity_t PreStore = EcsPreStore; +static const ecs_entity_t OnStore = EcsOnStore; +static const ecs_entity_t PostFrame = EcsPostFrame; + +/** Builtin entity ids */ +static const ecs_entity_t World = EcsWorld; +static const ecs_entity_t Singleton = EcsSingleton; + +/** Builtin roles */ +static const ecs_entity_t Childof = ECS_CHILDOF; +static const ecs_entity_t Instanceof = ECS_INSTANCEOF; +static const ecs_entity_t Trait = ECS_TRAIT; +static const ecs_entity_t Switch = ECS_SWITCH; +static const ecs_entity_t Case = ECS_CASE; +static const ecs_entity_t Owned = ECS_OWNED; + +//////////////////////////////////////////////////////////////////////////////// + +/** Unsafe wrapper class around a column. + * This class can be used when a system does not know the type of a column at + * compile time. + */ +class unsafe_column { +public: + unsafe_column(void* array, size_t size, size_t count, bool is_shared = false) + : m_array(array) + , m_size(size) + , m_count(count) + , m_is_shared(is_shared) {} + + /** Return element in component array. + * This operator may only be used if the column is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + void* operator[](size_t index) { + ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + ecs_assert(!m_is_shared, ECS_INVALID_PARAMETER, NULL); + return ECS_OFFSET(m_array, m_size * index); + } + + /** Return whether component is set. + * If the column is optional, this method may return false. + * + * @return True if component is set, false if component is not set. + */ + bool is_set() const { + return m_array != nullptr; + } + + /** Return whether component is shared. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_shared() const { + return m_is_shared; + } + +protected: + void* m_array; + size_t m_size; + size_t m_count; + bool m_is_shared; +}; + +/** Wrapper class around a column. + * + * @tparam T component type of the column. + */ +template +class column { +public: + /** Create column from component array. + * + * @param array Pointer to the component array. + * @param count Number of elements in component array. + * @param is_shared Is the component shared or not. + */ + column(T* array, size_t count, bool is_shared = false) + : m_array(array) + , m_count(count) + , m_is_shared(is_shared) {} + + /** Create column from iterator. + * + * @param iter Iterator object. + * @param column Index of the signature of the query being iterated over. + */ + column(iter &iter, int column); + + /** Return element in component array. + * This operator may only be used if the column is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + T& operator[](size_t index) { + ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + ecs_assert(!index || !m_is_shared, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return m_array[index]; + } + + /** Return first element of component array. + * This operator is typically used when the column is shared. + * + * @return Reference to the first element. + */ + T* operator->() { + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return m_array; + } + + /** Return whether component is set. + * If the column is optional, this method may return false. + * + * @return True if component is set, false if component is not set. + */ + bool is_set() const { + return m_array != nullptr; + } + + /** Return whether component is shared. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_shared() const { + return m_is_shared; + } + +protected: + T* m_array; + size_t m_count; + bool m_is_shared; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +namespace _ { + +/** Similar to flecs::column, but abstracts away from shared / owned columns. + * + * @tparam T component type of the column. + */ +template +class any_column { }; + +template +class any_column::value == true>::type > final : public column::type> { +public: + any_column(T array, std::size_t count, bool is_shared = false) + : column::type>(array, count, is_shared) { } + + T operator[](size_t index) { + if (!this->m_is_shared) { + ecs_assert(index < this->m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + if (this->m_array) { + return &this->m_array[index]; + } else { + return nullptr; + } + } else { + return &this->m_array[0]; + } + } +}; + +template +class any_column::value == false>::type> final : public column { +public: + any_column(T* array, std::size_t count, bool is_shared = false) + : column(array, count, is_shared) { } + + T& operator[](size_t index) { + if (!this->m_is_shared) { + ecs_assert(index < this->m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return this->m_array[index]; + } else { + return this->m_array[0]; + } + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** Iterate over an integer range (used to iterate over entity range). + * + * @tparam Type of the iterator + */ +template +class range_iterator +{ +public: + explicit range_iterator(T value) + : m_value(value){} + + bool operator!=(range_iterator const& other) const + { + return m_value != other.m_value; + } + + T const& operator*() const + { + return m_value; + } + + range_iterator& operator++() + { + ++m_value; + return *this; + } + +private: + T m_value; +}; + +} // namespace _ + + +//////////////////////////////////////////////////////////////////////////////// + +/** Class that enables iterating over table columns. + */ +class iter final { + using row_iterator = _::range_iterator; +public: + /** Construct iterator from C iterator object. + * This operation is typically not invoked directly by the user. + * + * @param it Pointer to C iterator. + */ + iter(const ecs_iter_t *it) : m_iter(it) { + m_begin = 0; + m_end = static_cast(it->count); + } + + row_iterator begin() const { + return row_iterator(m_begin); + } + + row_iterator end() const { + return row_iterator(m_end); + } + + /** Obtain handle to current system. + */ + flecs::entity system() const; + + /** Obtain current world. + */ + flecs::world world() const; + + /** Number of entities to iterate over. + */ + size_t count() const { + return static_cast(m_iter->count); + } + + /** Number of columns in iteator. + */ + int32_t column_count() const { + return m_iter->column_count; + } + + /** Size of column data type. + * + * @param col The column id. + */ + size_t column_size(int32_t col) const { + return ecs_column_size(m_iter, col); + } + + /** Return delta_time of current frame. + */ + FLECS_FLOAT delta_time() const { + return m_iter->delta_time; + } + + /** Return time elapsed since last time system was invoked. + */ + FLECS_FLOAT delta_system_time() const { + return m_iter->delta_system_time; + } + + /** Return total time passed in simulation. + */ + FLECS_FLOAT world_time() const { + return m_iter->world_time; + } + + /** Returns whether column is shared. + * + * @param col The column id. + */ + bool is_shared(int32_t col) const { + return !ecs_is_owned(m_iter, col); + } + + /** Returns whether column is owned. + * + * @param col The column id. + */ + bool is_owned(int32_t col) const { + return ecs_is_owned(m_iter, col); + } + + /** Returns whether column is set. + * + * @param col The column id. + */ + bool is_set(int32_t col) const { + return ecs_column_w_size(m_iter, 0, col) != NULL; + } + + /** Access param field. + * The param field contains the value assigned to flecs::Context, or the + * value passed to the `param` argument when invoking system::run. + */ + void* param() { + return m_iter->param; + } + + /** Returns whether column is readonly. + * + * @param col The column id. + */ + bool is_readonly(int32_t col) const { + return ecs_is_readonly(m_iter, col); + } + + /** Obtain column source (0 if self) + * + * @param col The column id. + */ + flecs::entity column_source(int32_t col) const; + + /** Obtain component/tag entity of column. + * + * @param col The column id. + */ + flecs::entity column_entity(int32_t col) const; + + /** Obtain type of column + * + * @param col The column id. + */ + flecs::type column_type(int32_t col) const; + + /** Obtain entity being iterated over for row. + * + * @param row Row being iterated over. + */ + flecs::entity entity(size_t row) const; + + /** Obtain type of table being iterated over. + */ + type table_type() const; + + /** Obtain the total number of tables the iterator will iterate over. + */ + int32_t table_count() const { + return m_iter->table_count; + } + + /** Obtain the total number of inactive tables the query is matched with. + */ + int32_t inactive_table_count() const { + return m_iter->inactive_table_count; + } + + /** Obtain untyped pointer to table column. + * + * @param table_column Id of table column (corresponds with location in table type). + * @return Pointer to table column. + */ + void* table_column(int32_t col) const { + return ecs_table_column(m_iter, col); + } + + /** Obtain typed pointer to table column. + * If the table does not contain a column with the specified type, the + * function will assert. + * + * @tparam T Type of the table column. + */ + template + flecs::column table_column() const { + auto type = ecs_iter_type(m_iter); + auto col = ecs_type_index_of(type, _::component_info::id()); + ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); + return flecs::column(static_cast(ecs_table_column(m_iter, col)), + static_cast(m_iter->count), false); + } + + /** Obtain column with const type. + * If the specified column id does not match with the provided type, the + * function will assert. + * + * @tparam T Type of the column. + * @param col The column id. + * @return The component column. + */ + template ::value, void>::type* = nullptr> + flecs::column column(int32_t col) const { + return get_column(col); + } + + /** Obtain column with non-const type. + * If the specified column id does not match with the provided type or if + * the column is readonly, the function will assert. + * + * @tparam T Type of the column. + * @param col The column id. + * @return The component column. + */ + template ::value == false, void>::type* = nullptr> + flecs::column column(int32_t col) const { + ecs_assert(!ecs_is_readonly(m_iter, col), ECS_COLUMN_ACCESS_VIOLATION, NULL); + return get_column(col); + } + + /** Obtain unsafe column. + * Unsafe columns are required when a system does not know at compile time + * which component will be passed to it. + * + * @param col The column id. + */ + flecs::unsafe_column column(int32_t col) const { + return get_unsafe_column(col); + } + + /** Obtain owned column. + * Same as iter::column, but ensures that column is owned. + * + * @tparam Type of the column. + * @param col The column id. + * @return The component column. + */ + template + flecs::column owned(int32_t col) const { + ecs_assert(!!ecs_is_owned(m_iter, col), ECS_COLUMN_IS_SHARED, NULL); + return this->column(col); + } + + /** Obtain shared column. + * Same as iter::column, but ensures that column is shared. + * + * @tparam Type of the column. + * @param col The column id. + * @return The component column. + */ + template + const T& shared(int32_t col) const { + ecs_assert(ecs_column_entity(m_iter, col) == _::component_info::id(m_iter->world), ECS_COLUMN_TYPE_MISMATCH, NULL); + ecs_assert(!ecs_is_owned(m_iter, col), ECS_COLUMN_IS_NOT_SHARED, NULL); + return *static_cast(ecs_column_w_size(m_iter, sizeof(T), col)); + } + + /** Obtain single const element of owned or shared column. + * If the specified column id does not match with the provided type the + * function will assert. + * + * @tparam Type of the element. + * @param col The column id. + * @param row The current row. + * @return The component element. + */ + template ::value, void>::type* = nullptr> + T& element(int32_t col, int32_t row) const { + return get_element(col, row); + } + + /** Obtain single const element of owned or shared column. + * If the specified column id does not match with the provided type or if + * the column is readonly, the function will assert. + * + * @tparam Type of the element. + * @param col The column id. + * @param row The current row. + * @return The component element. + */ + template ::value == false, void>::type* = nullptr> + T& element(int32_t col, int32_t row) const { + ecs_assert(!ecs_is_readonly(m_iter, col), ECS_COLUMN_ACCESS_VIOLATION, NULL); + return get_element(col, row); + } + +private: + /* Get column, check if correct type is used */ + template + flecs::column get_column(int32_t column_id) const { +#ifndef NDEBUG + ecs_entity_t column_entity = ecs_column_entity(m_iter, column_id); + ecs_assert(column_entity & ECS_TRAIT || column_entity & ECS_SWITCH || + column_entity & ECS_CASE || + column_entity == _::component_info::id(m_iter->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif + + size_t count; + bool is_shared = !ecs_is_owned(m_iter, column_id); + + /* If a shared column is retrieved with 'column', there will only be a + * single value. Ensure that the application does not accidentally read + * out of bounds. */ + if (is_shared) { + count = 1; + } else { + /* If column is owned, there will be as many values as there are + * entities. */ + count = static_cast(m_iter->count); + } + + return flecs::column( + static_cast(ecs_column_w_size(m_iter, sizeof(T), column_id)), + count, is_shared); + } + + flecs::unsafe_column get_unsafe_column(int32_t column_id) const { + size_t count; + size_t size = ecs_column_size(m_iter, column_id); + bool is_shared = !ecs_is_owned(m_iter, column_id); + + /* If a shared column is retrieved with 'column', there will only be a + * single value. Ensure that the application does not accidentally read + * out of bounds. */ + if (is_shared) { + count = 1; + } else { + /* If column is owned, there will be as many values as there are + * entities. */ + count = static_cast(m_iter->count); + } + + return flecs::unsafe_column(ecs_column_w_size(m_iter, 0, column_id), size, count, is_shared); + } + + /* Get single field, check if correct type is used */ + template + T& get_element(int32_t col, int32_t row) const { + ecs_assert(ecs_column_entity(m_iter, col) == _::component_info::id(m_iter->world), ECS_COLUMN_TYPE_MISMATCH, NULL); + return *static_cast(ecs_element_w_size(m_iter, sizeof(T), col, row)); + } + + const ecs_iter_t *m_iter; + std::size_t m_begin; + std::size_t m_end; +}; + +template +inline column::column(iter &iter, int32_t col) { + *this = iter.column(col); +} + + +//////////////////////////////////////////////////////////////////////////////// + +/** The world. + * The world is the container of all ECS data and systems. If the world is + * deleted, all data in the world will be deleted as well. + */ +class world final { +public: + /** Create world. + */ + world() + : m_world( ecs_init() ) + , m_owned( true ) { init_builtin_components(); } + + /** Create world with command line arguments. + * Currently command line arguments are not interpreted, but they may be + * used in the future to configure Flecs parameters. + */ + world(int argc, char *argv[]) + : m_world( ecs_init_w_args(argc, argv) ) + , m_owned( true ) { init_builtin_components(); } + + /** Create world from C world. + */ + explicit world(world_t *w) + : m_world( w ) + , m_owned( false ) { } + + /** Not allowed to copy a world. May only take a reference. + */ + world(const world& obj) = delete; + + world(world&& obj) { + m_world = obj.m_world; + m_owned = obj.m_owned; + obj.m_world = nullptr; + obj.m_owned = false; + } + + /** Not allowed to copy a world. May only take a reference. + */ + world& operator=(const world& obj) = delete; + + world& operator=(world&& obj) { + this->~world(); + + m_world = obj.m_world; + m_owned = obj.m_owned; + obj.m_world = nullptr; + obj.m_owned = false; + return *this; + } + + ~world() { + if (m_owned && m_world) { + ecs_fini(m_world); + } + } + + /** Obtain pointer to C world object. + */ + world_t* c_ptr() const { + return m_world; + } + + /** Enable tracing. + * + * @param level The tracing level. + */ + static void enable_tracing(int level) { + ecs_tracing_enable(level); + } + + /** Progress world, run all systems. + * + * @param delta_time Custom delta_time. If 0 is provided, Flecs will automatically measure delta_tiem. + */ + bool progress(FLECS_FLOAT delta_time = 0.0) const { + return ecs_progress(m_world, delta_time); + } + + /** Defer operations until end of frame. + * When this operation is invoked while iterating, operations inbetween the + * defer_begin and defer_end operations are executed at the end of the frame. + * + * This operation is thread safe. + */ + bool defer_begin() { + return ecs_defer_begin(m_world); + } + + /** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + */ + bool defer_end() { + return ecs_defer_end(m_world); + } + + /** Set number of threads. + * This will distribute the load evenly across the configured number of + * threads for each system. + * + * @param threads Number of threads. + */ + void set_threads(std::int32_t threads) const { + ecs_set_threads(m_world, threads); + } + + /** Get number of threads. + * + * @return Number of configured threads. + */ + std::int32_t get_threads() const { + return ecs_get_threads(m_world); + } + + /** Get index of current thread. + * + * @return Unique index for current thread. + */ + std::int32_t get_thread_index() const { + return ecs_get_thread_index(m_world); + } + + /** Set target FPS + * This will ensure that the main loop (world::progress) does not run faster + * than the specified frames per second. + * + * @param target_fps Target frames per second. + */ + void set_target_fps(FLECS_FLOAT target_fps) const { + ecs_set_target_fps(m_world, target_fps); + } + + /** Get target FPS + * + * @return Configured frames per second. + */ + FLECS_FLOAT get_target_fps() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->target_fps; + } + + /** Get tick + * + * @return Monotonically increasing frame count. + */ + std::int32_t get_tick() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->frame_count_total; + } + + /** Set timescale + * + * @return Monotonically increasing frame count. + */ + void set_time_scale(FLECS_FLOAT mul) const { + ecs_set_time_scale(m_world, mul); + } + + /** Get timescale + * + * @return Monotonically increasing frame count. + */ + FLECS_FLOAT get_time_scale() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->time_scale; + } + + /** Set world context. + * Set a context value that can be accessed by anyone that has a reference + * to the world. + * + * @param ctx The world context. + */ + void set_context(void* ctx) const { + ecs_set_context(m_world, ctx); + } + + /** Get world context. + * + * @return The configured world context. + */ + void* get_context() const { + return ecs_get_context(m_world); + } + + /** Preallocate memory for number of entities. + * This function preallocates memory for the entity index. + * + * @param entity_count Number of entities to preallocate memory for. + */ + void dim(std::int32_t entity_count) const { + ecs_dim(m_world, entity_count); + } + + /** Preallocate memory for type + * This function preallocates memory for the component arrays of the + * specified type. + * + * @param type Type to preallocate memory for. + * @param entity_count Number of entities to preallocate memory for. + */ + void dim_type(type_t t, std::int32_t entity_count) const { + ecs_dim_type(m_world, t, entity_count); + } + + /** Set entity range. + * This function limits the range of issued entity ids between min and max. + * + * @param min Minimum entity id issued. + * @param max Maximum entity id issued. + */ + void set_entity_range(entity_t min, entity_t max) const { + ecs_set_entity_range(m_world, min, max); + } + + /** Enforce that operations cannot modify entities outside of range. + * This function ensures that only entities within the specified range can + * be modified. Use this function if specific parts of the code only are + * allowed to modify a certain set of entities, as could be the case for + * networked applications. + * + * @param enabled True if range check should be enabled, false if not. + */ + void enable_range_check(bool enabled) const { + ecs_enable_range_check(m_world, enabled); + } + + /** Disables inactive systems. + * + * This removes systems that are not matched with any entities from the main + * loop. Systems are only added to the main loop after they first match with + * entities, but are not removed automatically. + * + * This function allows an application to manually disable inactive systems + * which removes them from the main loop. Doing so will cause Flecs to + * rebuild the pipeline in the next iteration. + * + * @param level The tracing level. + */ + void deactivate_systems() { + ecs_deactivate_systems(m_world); + } + + /** Lookup entity by name. + * + * @param name Entity name. + */ + flecs::entity lookup(const char *name) const; + + /** Lookup entity by name. + * + * @overload + */ + flecs::entity lookup(std::string& name) const; + + /** Set singleton component. + */ + template + void set(T value) const; + + /** Get mut singleton component. + */ + template + T* get_mut() const; + + /** Mark singleton component as modified. + */ + template + void modified() const; + + /** Patch singleton component. + */ + template + void patch(std::function func) const; + + /** Get singleton component. + */ + template + const T* get() const; + + /** Test if world has singleton component. + */ + template + bool has() const; + + /** Remove singleton component. + */ + template + void remove() const; + + /** Get id for type. + */ + template + entity_t type_id() { + return _::component_info::id(m_world); + } + + /** Get singleton entity for type. + */ + template + flecs::entity singleton(); + + /** Create alias for component. + * + * @tparam Component to create an alias for. + * @param alias Alias for the component. + */ + template + flecs::entity use(const char *alias = nullptr); + + /** Create alias for entity. + * + * @param name Name of the entity. + * @param alias Alias for the entity. + */ + flecs::entity use(const char *name, const char *alias = nullptr); + + /** Create alias for entity. + * + * @param entity Entity for which to create the alias. + * @param alias Alias for the entity. + */ + void use(flecs::entity entity, const char *alias = nullptr); + + /** Delete all entities matching a filter. + * + * @param filter The filter to use for matching. + */ + void delete_entities(flecs::filter filter) const; + + /** Add component to all entities matching a filter. + * + * @tparam T The component to add. + * @param filter The filter to use for matching. + */ + template + void add(flecs::filter filter) const; + + /** Add type to all entities. + * + * @param type The type to add. + */ + void add(flecs::type type) const; + + /** Add type to all entities matching a filter. + * + * @param type The type to add. + * @param filter The filter to use for matching. + */ + void add(flecs::type type, flecs::filter filter) const; + + /** Add entity to all entities. + * + * @param entity The entity to add. + */ + void add(flecs::entity entity) const; + + /** Add entity to all entities matching a filter. + * + * @param entity The entity to add. + * @param filter The filter to use for matching. + */ + void add(flecs::entity entity, flecs::filter filter) const; + + /** Remove component from all entities matching a filter. + * + * @tparam T The component to remove. + * @param filter The filter to use for matching. + */ + template + void remove(flecs::filter filter) const; + + /** Remove type from all entities. + * + * @param type The component to remove. + */ + void remove(flecs::type type) const; + + /** Remove type from all entities matching a filter. + * + * @tparam T The component to remove. + * @param filter The filter to use for matching. + */ + void remove(flecs::type type, flecs::filter filter) const; + + /** Remove entity from all entities. + * + * @param entity The entity to remove. + */ + void remove(flecs::entity entity) const; + + /** Remove entity from all entities matching a filter. + * + * @param entity The entity to remove. + * @param filter The filter to use for matching. + */ + void remove(flecs::entity entity, flecs::filter filter) const; + + /** Create iterable filter for entities in world. + * + * @param filter The filter to create. + */ + world_filter filter(const flecs::filter& filter) const; + + flecs::filter_iterator begin() const; + flecs::filter_iterator end() const; + + /** Count entities matching a component. + * + * @tparam T The component to use for matching. + */ + template + int count() const { + return ecs_count_type( + m_world, _::component_info::type(m_world)); + } + + /** Count entities matching a filter. + * + * @param filter The filter to use for matching. + */ + int count(flecs::filter filter) const; + + /** Enable locking. + * + * @param enabled True if locking should be enabled, false if not. + */ + bool enable_locking(bool enabled) { + return ecs_enable_locking(m_world, enabled); + } + + /** Lock world. + */ + void lock() { + ecs_lock(m_world); + } + + /** Unlock world. + */ + void unlock() { + ecs_unlock(m_world); + } + + /** Create a prefab. + */ + template + flecs::entity entity(Args &&... args) const; + + /** Create an entity. + */ + template + flecs::entity prefab(Args &&... args) const; + + /** Create a type. + */ + template + flecs::type type(Args &&... args) const; + + /** Create a module. + */ + template + flecs::entity module(Args &&... args) const; + + /** Import a module. + */ + template + flecs::entity import(); // Cannot be const because modules accept a non-const world + + /** Create an system. + */ + template + flecs::system system(Args &&... args) const; + + /** Create a query. + */ + template + flecs::query query(Args &&... args) const; + + /** Register a component. + */ + template + flecs::entity component(Args &&... args) const; + + /** Register a POD component. + */ + template + flecs::entity pod_component(Args &&... args) const; + + /** Register a relocatable component. + */ + template + flecs::entity relocatable_component(Args &&... args) const; + + /** Create a snapshot. + */ + template + flecs::snapshot snapshot(Args &&... args) const; + +private: + void init_builtin_components(); + + world_t *m_world; + bool m_owned; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +/** Fluent API for chaining entity operations + * This class contains entity operations that can be chained. For example, by + * using this class, an entity can be created like this: + * + * flecs::entity e = flecs::entity(world) + * .add() + * .add(); + */ +template +class entity_builder { + using base_type = const Base; +public: + + /** Add an entity to an entity by id. + * This adds a raw entity id (64 bit integer) to the type of the current + * entity. + * + * @param entity The entity id to add. + */ + base_type& add(entity_t entity) const { + static_cast(this)->invoke( + [entity](world_t *world, entity_t id) { + ecs_add_entity(world, id, entity); + }); + return *static_cast(this); + } + + /** Add a component to an entity. + * To ensure the component is initialized, it should have a constructor. + * + * @tparam T the component type to add. + */ + template + base_type& add() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_add_entity(world, id, _::component_info::id(world)); + }); + return *static_cast(this); + } + + /** Add an entity to an entity. + * Add an entity to the entity. This is typically used for tagging. + * + * @param entity The entity to add. + */ + base_type& add(const entity& entity) const; + + /** Add a type to an entity by its C pointer. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type The C type to add. + */ + base_type& add(type_t type) const { + static_cast(this)->invoke( + [type](world_t *world, entity_t id) { + ecs_add_type(world, id, type); + }); + return *static_cast(this); + } + + /** Add a type to an entity. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type The type to add. + */ + base_type& add(type type) const; + + /** Add a trait. + * This operation adds a trait for an entity by entity id. If the trait + * is a component, a value of the trait type will be associated with the + * entity. If the trait is not a component, a value of the component type + * will be associated with the entity. If both the trait and component ids + * are regular entities, no values will be associated with the entity. + * + * @param trait The trait id. + * @param entity The entity identifier. + */ + base_type& add_trait(entity_t trait, entity_t entity) const { + static_cast(this)->invoke( + [entity, trait](world_t *world, entity_t id) { + ecs_add_entity(world, id, + ecs_trait(entity, trait)); + }); + return *static_cast(this); + } + + /** Add a trait. + * This operation adds a trait for a component. A value of the trait type + * will be associated with the entity. + * + * @tparam T the trait type. + * @tparam C the component type. + */ + template + base_type& add_trait() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_add_entity(world, id, + ecs_trait(_::component_info::id(world), + _::component_info::id(world))); + }); + return *static_cast(this); + } + + /** Add a trait. + * This operation adds a trait for a component. A value of the trait + * type will be associated with the entity. + * + * @tparam T The trait to add. + * @param component The component for which to add the trait. + */ + template + base_type& add_trait(flecs::entity component) const; + + /** Add a trait tag. + * This operation adds a trait tag for a component. A value of the component + * type will be associated with the entity. Note that the trait tag passed + * into this function should not be a component. + * + * @tparam C The component type. + * @param trait The trait identifier. + */ + template + base_type& add_trait_tag(flecs::entity trait) const; + + /** Add a trait. + * This operation adds a trait for an entity by entity id. If the trait + * is a component, a value of the trait type will be associated with the + * entity. If the trait is not a component, a value of the component type + * will be associated with the entity. If both the trait and component ids + * are regular entities, no values will be associated with the entity. + * + * @param trait The trait to add. + * @param entity The tag for which to add the trait. + */ + base_type& add_trait(flecs::entity trait, flecs::entity entity) const; + + /** Remove an entity from an entity by id. + * + * @param entity The entity id to remove. + */ + base_type& remove(entity_t entity) const { + static_cast(this)->invoke( + [entity](world_t *world, entity_t id) { + ecs_remove_entity(world, id, entity); + }); + return *static_cast(this); + } + + /** Remove a component from an entity. + * + * @tparam T the type of the component to remove. + */ + template + base_type& remove() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_remove_entity(world, id, _::component_info::id(world)); + }); + return *static_cast(this); + } + + /** Remove an entity from an entity. + * + * @param entity The entity to remove. + */ + base_type& remove(const entity& entity) const; + + /** Remove a type from an entity by its C pointer. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type the pointer to the type to remove. + */ + base_type& remove(type_t type) const { + static_cast(this)->invoke( + [type](world_t *world, entity_t id) { + ecs_remove_type(world, id, type); + }); + return *static_cast(this); + } + + /** Remove a type from an entity. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type the type to remove. + */ + base_type& remove(type type) const; + + /** Remove a trait. + * This operation removes a trait for an entity by entity id. + * + * @param trait The trait to remove. + * @param entity The entity for which to remove the trait. + */ + base_type& remove_trait(entity_t trait, entity_t entity) const { + static_cast(this)->invoke( + [entity, trait](world_t *world, entity_t id) { + ecs_remove_entity(world, id, + ecs_trait(entity, trait)); + }); + return *static_cast(this); + } + + /** Remove a trait. + * This operation removes a trait for a component. + * + * @tparam T The trait to remove. + * @tparam C The component for which to remove the trait. + */ + template + base_type& remove_trait() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_remove_entity(world, id, + ecs_trait(_::component_info::id(world), + _::component_info::id(world))); + }); + return *static_cast(this); + } + + /** Remove a trait. + * This operation removes a trait tag for a component. The trait should not + * be a component. + * + * @tparam T The trait to remove. + * @param component The component for which to remove the trait. + */ + template + base_type& remove_trait(flecs::entity component) const; + + /** Remove a trait tag. + * This operation removes a trait tag for a component. The trait should not + * be a component. + * + * @tparam C The component for which to remove the trait. + * @param trait The trait to remove. + */ + template + base_type& remove_trait_tag(flecs::entity trait) const; + + /** Remove a trait. + * This operation removes a trait for an entity. + * + * @param trait The trait to remove. + * @param entity The entity for which to remove the trait. + */ + base_type& remove_trait(flecs::entity trait, flecs::entity entity) const; + + /** Add a parent entity to an entity by id. + * + * @param parent The id of the parent to add. + */ + base_type& add_childof(entity_t parent) const { + static_cast(this)->invoke( + [parent](world_t *world, entity_t id) { + ecs_add_entity(world, id, ECS_CHILDOF | parent); + }); + return *static_cast(this); + } + + /** Add a parent entity to an entity. + * + * @param parent The parent to add. + */ + base_type& add_childof(const entity& parent) const; + + /** Remove a parent entity from an entity by id. + * + * @param parent The id of the parent to remove. + */ + base_type& remove_childof(entity_t parent) const { + static_cast(this)->invoke( + [parent](world_t *world, entity_t id) { + ecs_remove_entity(world, id, ECS_CHILDOF | parent); + }); + return *static_cast(this); + } + + /** Remove a parent entity from an entity. + * + * @param parent The parent to remove. + */ + base_type& remove_childof(const entity& parent) const; + + /** Add a base entity to an entity by id. + * + * @param base The base id to add. + */ + base_type& add_instanceof(entity_t base) const { + static_cast(this)->invoke( + [base](world_t *world, entity_t id) { + ecs_add_entity(world, id, ECS_INSTANCEOF | base); + }); + return *static_cast(this); + } + + /** Add a base entity to an entity. + * + * @param base The base to add. + */ + base_type& add_instanceof(const entity& base) const; + + /** Remove a base entity from an entity by id. + * + * @param base The base id to remove. + */ + base_type& remove_instanceof(entity_t base) const { + static_cast(this)->invoke( + [base](world_t *world, entity_t id) { + ecs_remove_entity(world, id, ECS_INSTANCEOF | base); + }); + return *static_cast(this); + } + + /** Remove a base entity from an entity. + * + * @param base The base to remove. + */ + base_type& remove_instanceof(const entity& base) const; + + /** Add owned flag for component (forces ownership when instantiating) + * + * @param entity The entity for which to add the OWNED flag + */ + base_type& add_owned(entity_t entity) const { + static_cast(this)->invoke( + [entity](world_t *world, entity_t id) { + ecs_add_entity(world, id, ECS_OWNED | entity); + }); + return *static_cast(this); + } + + /** Add owned flag for component (forces ownership when instantiating) + * + * @tparam T The component for which to add the OWNED flag + */ + template + base_type& add_owned() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_add_entity(world, id, ECS_OWNED | _::component_info::id(world)); + }); + return *static_cast(this); + } + + /** Add owned flag for type entity. + * This will ensure that all components in the type are owned for instances + * of this entity. + * + * @param type The type for which to add the OWNED flag + */ + base_type& add_owned(flecs::type type) const; + + /** Add a switch to an entity by id. + * The switch entity must be a type, that is it must have the EcsType + * component. Entities created with flecs::type are valid here. + * + * @param sw The switch entity id to add. + */ + base_type& add_switch(entity_t sw) const { + static_cast(this)->invoke( + [sw](world_t *world, entity_t id) { + ecs_add_entity(world, id, ECS_SWITCH | sw); + }); + return *static_cast(this); + } + + /** Add a switch to an entity. + * The switch entity must be a type, that is it must have the EcsType + * component. + * + * @param sw The switch entity to add. + */ + base_type& add_switch(const entity& sw) const; + + /** Add a switch to an entity. + * Any instance of flecs::type can be used as a switch. + * + * @param sw The switch to add. + */ + base_type& add_switch(const type& sw) const; + + /** Remove a switch from an entity by id. + * + * @param sw The switch entity id to remove. + */ + base_type& remove_switch(entity_t sw) const { + static_cast(this)->invoke( + [sw](world_t *world, entity_t id) { + ecs_remove_entity(world, id, ECS_SWITCH | sw); + }); + return *static_cast(this); + } + + /** Remove a switch from an entity. + * + * @param sw The switch entity to remove. + */ + base_type& remove_switch(const entity& sw) const; + + /** Remove a switch from an entity. + * Any instance of flecs::type can be used as a switch. + * + * @param sw The switch to remove. + */ + base_type& remove_switch(const type& sw) const; + + /** Add a switch to an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity id to add. + */ + base_type& add_case(entity_t sw_case) const { + static_cast(this)->invoke( + [sw_case](world_t *world, entity_t id) { + ecs_add_entity(world, id, ECS_CASE | sw_case); + }); + return *static_cast(this); + } + + /** Add a switch to an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @tparam T The case to add. + */ + template + base_type& add_case() const { + return this->add_case(_::component_info::id()); + } + + /** Add a case to an entity. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity to add. + */ + base_type& add_case(const entity& sw_case) const; + + /** Remove a case from an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity id to remove. + */ + base_type& remove_case(entity_t sw_case) const { + static_cast(this)->invoke( + [sw_case](world_t *world, entity_t id) { + ecs_remove_entity(world, id, ECS_CASE | sw_case); + }); + return *static_cast(this); + } + + /** Remove a switch from an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @tparam T The case to remove. + */ + template + base_type& remove_case() const { + return this->remove_case(_::component_info::id()); + } + + /** Remove a case from an entity. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity id to remove. + */ + base_type& remove_case(const entity& sw_case) const; + + /** Enable an entity. + * Enabled entities are matched with systems and can be searched with + * queries. + */ + base_type& enable() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_enable(world, id, true); + }); + return *static_cast(this); + } + + /** Disable an entity. + * Disabled entities are not matched with systems and cannot be searched + * with queries, unless explicitly specified in the query expression. + */ + base_type& disable() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_enable(world, id, true); + }); + return *static_cast(this); + } + + /** Enable a component. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @tparam T The component to enable. + */ + template + base_type& enable() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_enable_component_w_entity(world, id, _::component_info::id(), true); + }); + return *static_cast(this); + } + + /** Disable a component. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @tparam T The component to enable. + */ + template + base_type& disable() const { + static_cast(this)->invoke( + [](world_t *world, entity_t id) { + ecs_enable_component_w_entity(world, id, _::component_info::id(), false); + }); + return *static_cast(this); + } + + /** Enable a component. + * See enable. + * + * @param id The component to enable. + */ + base_type& enable(flecs::entity_t id) const { + static_cast(this)->invoke( + [id](world_t *world, entity_t e) { + ecs_enable_component_w_entity(world, e, id, true); + }); + return *static_cast(this); + } + + /** Disable a component. + * See disable. + * + * @param id The component to disable. + */ + base_type& disable(flecs::entity_t id) const { + static_cast(this)->invoke( + [id](world_t *world, entity_t e) { + ecs_enable_component_w_entity(world, e, id, false); + }); + return *static_cast(this); + } + + /** Enable a component. + * See enable. + * + * @param entity The component to enable. + */ + base_type& enable(const flecs::entity& entity) const; + + /** Disable a component. + * See disable. + * + * @param entity The component to disable. + */ + base_type& disable(const flecs::entity& entity) const; + + /** Set a component for an entity. + * This operation overwrites the component value. If the entity did not yet + * have the component, this operation will add it. + * + * @tparam T The component to set. + * @param value The value to assign to the component. + */ + template + const base_type& set(T&& value) const { + static_cast(this)->invoke( + [&value](world_t *world, entity_t id) { + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + ecs_set_ptr_w_entity( + world, id, _::component_info::id(world), sizeof(T), &value); + }); + return *static_cast(this); + } + + /** Set a component for an entity. + * This operation overwrites the component value. If the entity did not yet + * have the component, this operation will add it. + * + * @tparam T The component to set. + * @param value The value to assign to the component. + */ + template + const base_type& set(const T& value) const { + static_cast(this)->invoke( + [&value](world_t *world, entity_t id) { + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + ecs_set_ptr_w_entity( + world, id, _::component_info::id(world), sizeof(T), &value); + }); + return *static_cast(this); + } + + /** Set a trait for an entity. + * This operation overwrites the trait value. If the entity did not yet + * have the trait, this operation will add it. + * + * @tparam T The trait to set. + * @tparam C The component for which to set the trait. + * @param value The value to assign to the trait. + */ + template + const base_type& set_trait(const T& value) const { + static_cast(this)->invoke( + [&value](world_t *world, entity_t id) { + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + ecs_set_ptr_w_entity(world, id, + ecs_trait(_::component_info::id(world), + _::component_info::id(world)), + sizeof(T), &value); + }); + return *static_cast(this); + } + + /** Set a trait tag for a component. + * This operation overwrites the trait value. If the entity did not yet + * have the trait, this operation will add it. + * + * This operation should be used for traits that are not components. If a + * trait is not a component, it will assume the type of the component it is + * assigned to. + * + * @tparam C The component for which to set the trait. + * @param value The value to assign to the trait. + */ + template + const base_type& set_trait_tag(flecs::entity trait, const C& value) const; + + /** Set a trait for an entity. + * This operation overwrites the trait value. If the entity did not yet + * have the trait, this operation will add it. + * + * @tparam T The trait to set. + * @param value The value to assign to the trait. + * @param entity The entity for which to set the trait. + */ + template + const base_type& set_trait(const T& value, flecs::entity entity) const; + + /** Patch a component value. + * This operation allows an application to partially overwrite a component + * value. The operation invokes a function with a reference to the value to + * write, and a boolean indicating if the component already existed. + * + * @tparam T The component to patch. + * @param func The function invoked by this operation. + */ + template + const base_type& patch(std::function func) const { + static_cast(this)->invoke( + [&func](world_t *world, entity_t id) { + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + + bool is_added; + T *ptr = static_cast(ecs_get_mut_w_entity( + world, id, _::component_info::id(world), &is_added)); + if (ptr) { + func(*ptr, !is_added); + ecs_modified_w_entity(world, id, _::component_info::id(world)); + } + }); + return *static_cast(this); + } + + /** Patch a component value. + * This operation allows an application to partially overwrite a component + * value. The operation invokes a function with a reference to the value to + * write. + * + * @tparam T The component to patch. + * @param func The function invoked by this operation. + */ + template + const base_type& patch(std::function func) const { + static_cast(this)->invoke( + [&func](world_t *world, entity_t id) { + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + + bool is_added; + T *ptr = static_cast(ecs_get_mut_w_entity( + world, id, _::component_info::id(world), &is_added)); + if (ptr) { + func(*ptr); + ecs_modified_w_entity(world, id, _::component_info::id(world)); + } + }); + return *static_cast(this); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Quick and safe access to a component pointer +//////////////////////////////////////////////////////////////////////////////// + +template +class ref { +public: + ref() + : m_world( nullptr ) + , m_entity( 0 ) + , m_ref() + { + } + + ref(world_t *world, entity_t entity) + : m_world( world ) + , m_entity( entity ) + , m_ref() + { + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + + ecs_get_ref_w_entity( + m_world, &m_ref, m_entity, _::component_info::id(world)); + } + + const T* operator->() { + const T* result = static_cast(ecs_get_ref_w_entity( + m_world, &m_ref, m_entity, _::component_info::id(m_world))); + + ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); + + return result; + } + + const T* get() { + if (m_entity) { + ecs_get_ref_w_entity( + m_world, &m_ref, m_entity, _::component_info::id(m_world)); + } + + return static_cast(m_ref.ptr); + } + + flecs::entity entity() const; + +private: + world_t *m_world; + entity_t m_entity; + ecs_ref_t m_ref; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +/** Entity class + * This class provides access to entity operations. + */ +class entity : public entity_builder { +public: + /** Default constructor. + */ + explicit entity() + : m_world( nullptr ) + , m_id( 0 ) { } + + /** Create entity. + * + * @param world The world in which to create the entity. + */ + explicit entity(const world& world) + : m_world( world.c_ptr() ) + , m_id( ecs_new_w_type(m_world, 0) ) { } + + /** Create entity. + * + * @param world Pointer to the world in which to create the entity. + */ + explicit entity(world_t *world) + : m_world( world ) + { + if (m_world) { + m_id = ecs_new_w_type(m_world, 0); + } + } + + /** Create a named entity. + * Named entities can be looked up with the lookup functions. Entity names + * may be scoped, where each element in the name is separated by "::". + * For example: "Foo::Bar". If parts of the hierarchy in the scoped name do + * not yet exist, they will be automatically created. + * + * @param world The world in which to create the entity. + * @param name The entity name. + * @param is_component If true, the entity will be created from the pool of component ids (default = false). + */ + entity(const world& world, const char *name, bool is_component = false) + : m_world( world.c_ptr() ) + , m_id( ecs_lookup_path_w_sep(m_world, 0, name, "::", "::") ) + { + if (!m_id) { + if (is_component) { + m_id = ecs_new_component_id(m_world); + } + + m_id = ecs_add_path_w_sep( + m_world, m_id, 0, name, "::", "::"); + } + } + + /** Create a named entity. + * Named entities can be looked up with the lookup functions. Entity names + * may be scoped, where each element in the name is separated by "::". + * For example: "Foo::Bar". If parts of the hierarchy in the scoped name do + * not yet exist, they will be automatically created. + * + * @param world The world in which to create the entity. + * @param name The entity name. + * @param is_component If true, the entity will be created from the pool of component ids (default = false). + */ + entity(const world& world, std::string name, bool is_component = false) + : m_world( world.c_ptr() ) + , m_id( ecs_lookup_path_w_sep(m_world, 0, name.c_str(), "::", "::") ) + { + if (!m_id) { + if (is_component) { + m_id = ecs_new_component_id(m_world); + } + + m_id = ecs_add_path_w_sep( + m_world, m_id, 0, name.c_str(), "::", "::"); + } + } + + /** Wrap an existing entity id. + * + * @param world The world in which the entity is created. + * @param id The entity id. + */ + entity(const world& world, entity_t id) + : m_world( world.c_ptr() ) + , m_id(id) { } + + /** Wrap an existing entity id. + * + * @param world Pointer to the world in which the entity is created. + * @param id The entity id. + */ + entity(world_t *world, entity_t id) + : m_world( world ) + , m_id(id) { } + + /** Equality operator. */ + bool operator==(const entity& e) { + return this->id() == e.id(); + } + + /** Inequality operator. */ + bool operator!=(const entity& e) { + return this->id() != e.id(); + } + + explicit operator bool() { + return m_id != 0; + } + + /** Entity id 0. + * This function is useful when the API must provide an entity object that + * belongs to a world, but the entity id is 0. + * + * @param world The world. + */ + static + flecs::entity null(const world& world) { + return flecs::entity(world.c_ptr(), (ecs_entity_t)0); + } + + static + flecs::entity null() { + return flecs::entity(nullptr, (ecs_entity_t)0); + } + + /** Get entity id. + * @return The integer entity id. + */ + entity_t id() const { + return m_id; + } + + /** Get lo entity id. + * @return A new entity containing the lower 32 bits of the entity id. + */ + flecs::entity lo() const { + return flecs::entity(m_world, ecs_entity_t_lo(m_id)); + } + + /** Get hi entity id. + * @return A new entity containing the higher 32 bits of the entity id. + */ + flecs::entity hi() const { + return flecs::entity(m_world, ecs_entity_t_hi(m_id)); + } + + /** Combine two entity ids. + * @return A new entity that combines the provided entity ids in the lower + * and higher 32 bits of the entity id. + */ + static + flecs::entity comb(flecs::entity lo, flecs::entity hi) { + return flecs::entity(lo.world(), + ecs_entity_t_comb(lo.id(), hi.id())); + } + + /** Add role. + * Roles are added to entity ids in types to indicate which role they play. + * Examples of roles are flecs::Instanceof and flecs::Childof. + * + * @return A new entity with the specified role set. + */ + flecs::entity add_role(entity_t role) const { + return flecs::entity(m_world, m_id | role); + } + + /** Remove role. + * Roles are added to entity ids in types to indicate which role they play. + * Examples of roles are flecs::Instanceof and flecs::Childof. + * + * @return A new entity with any roles removed. + */ + flecs::entity remove_role() const { + return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); + } + + /** Check if entity has specified role. + * Roles are added to entity ids in types to indicate which role they play. + * Examples of roles are flecs::Instanceof and flecs::Childof. + * + * @return True if the entity has the role, false otherwise. + */ + bool has_role(entity_t role) const { + return ((m_id & ECS_ROLE_MASK) == role); + } + + /** Check is entity is alive. + * + * @return True if the entity is alive, false otherwise. + */ + bool is_alive() { + return ecs_is_alive(m_world, m_id); + } + + /** Return the entity name. + * + * @return The entity name, or an empty string if the entity has no name. + */ + std::string name() const { + const EcsName *name = static_cast( + ecs_get_w_entity(m_world, m_id, ecs_entity(EcsName))); + if (name && name->value) { + return std::string(name->value); + } else { + return std::string(); + } + } + + /** Return the entity path. + * + * @return The hierarchical entity path, or an empty string if the entity + * has no name. + */ + std::string path(const char *sep = "::", const char *init_sep = "::") const { + char *path = ecs_get_path_w_sep(m_world, 0, m_id, 0, sep, init_sep); + if (path) { + std::string result = std::string(path); + ecs_os_free(path); + return result; + } else { + return std::string(); + } + } + + bool enabled() { + return !ecs_has_entity(m_world, m_id, flecs::Disabled); + } + + /** Return the world. + * + * @return The world the entity is stored in. + */ + flecs::world world() const { + return flecs::world(m_world); + } + + /** Return the type. + * + * @return Returns the entity type. + */ + flecs::type type() const; + + /** Return type containing the entity. + * + * @return A type that contains only this entity. + */ + flecs::type to_type() const; + + /** Get component value. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template + const T* get() const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_w_entity(m_world, m_id, _::component_info::id(m_world))); + } + + /** Get component value (untyped). + * + * @param component The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + const void* get(flecs::entity component) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_get_w_entity(m_world, m_id, component.id()); + } + + /** Get component value (untyped). + * + * @param component The id of the component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + const void* get(entity_t component_id) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_get_w_entity(m_world, m_id, component_id); + } + + /** Get mutable component value. + * This operation returns a mutable pointer to the component. If the entity + * did not yet have the component, it will be added. If a base entity had + * the component, it will be overridden, and the value of the base component + * will be copied to the entity before this function returns. + * + * @tparam T The component to get. + * @param is_added If provided, this parameter will be set to true if the component was added. + * @return Pointer to the component value. + */ + template + T* get_mut(bool *is_added = nullptr) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_mut_w_entity( + m_world, m_id, _::component_info::id(m_world), is_added)); + } + + /** Get mutable component value (untyped). + * This operation returns a mutable pointer to the component. If the entity + * did not yet have the component, it will be added. If a base entity had + * the component, it will be overridden, and the value of the base component + * will be copied to the entity before this function returns. + * + * @param component The component to get. + * @param is_added If provided, this parameter will be set to true if the component was added. + * @return Pointer to the component value. + */ + void* get_mut(flecs::entity component, bool *is_added = nullptr) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_get_mut_w_entity(m_world, m_id, component.id(), is_added); + } + + /** Get mutable component value (untyped). + * This operation returns a mutable pointer to the component. If the entity + * did not yet have the component, it will be added. If a base entity had + * the component, it will be overridden, and the value of the base component + * will be copied to the entity before this function returns. + * + * @param component The id of the component to get. + * @param is_added If provided, this parameter will be set to true if the component was added. + * @return Pointer to the component value. + */ + void* get_mut(entity_t component_id, bool *is_added = nullptr) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_get_mut_w_entity(m_world, m_id, component_id, is_added); + } + + /** Get trait value. + * + * @tparam T The trait to get. + * @tparam C The component for which to get the trait. + * @return Pointer to the trait value, nullptr if the entity does not + * have the trait. + */ + template + const T* get_trait() const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast(ecs_get_w_entity(m_world, m_id, ecs_trait( + _::component_info::id(m_world), _::component_info::id(m_world)))); + } + + /** Get trait value. + * + * @tparam T The trait to get. + * @param component The component for which to get the trait. + * @return Pointer to the trait value, nullptr if the entity does not + * have the trait. + */ + template + const T* get_trait(flecs::entity component) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast(ecs_get_w_entity(m_world, m_id, ecs_trait( + component.id(), _::component_info::id(m_world)))); + } + + /** Get trait tag value. + * The trait passed to this function should not be a component. If a trait + * is not a component, the trait assumes the type of the component it is + * assigned to. + * + * @tparam C The component for which to get the trait + * @param trait The trait to get. + * @return Pointer to the trait value, nullptr if the entity does not + * have the trait. + */ + template + const C* get_trait_tag(flecs::entity trait) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast(ecs_get_w_entity(m_world, m_id, ecs_trait( + _::component_info::id(m_world), trait.id()))); + } + + /** Get trait tag value (untyped). + * If a trait is not a component, the trait assumes the type of the + * component it is assigned to. + * + * @param trait The trait to get. + * @param component The component for which to get the trait. + * @return Pointer to the trait value, nullptr if the entity does not + * have the trait. + */ + const void* get_trait(flecs::entity trait, flecs::entity component) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_get_w_entity(m_world, m_id, ecs_trait( + component.id(), trait.id())); + } + + /** Get mutable trait value. + * This operation returns a mutable pointer to the trait. If the entity + * did not yet have the trait, it will be added. If a base entity had + * the trait, it will be overridden, and the value of the base trait + * will be copied to the entity before this function returns. + * + * @tparam T The trait to get. + * @tparam C The component for which to get the trait. + * @param is_added If provided, this parameter will be set to true if the trait was added. + * @return Pointer to the trait value. + */ + template + T* get_trait_mut(bool *is_added = nullptr) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_mut_w_entity( + m_world, m_id, ecs_trait( + _::component_info::id(m_world), + _::component_info::id(m_world)), + is_added)); + } + + /** Get mutable trait value. + * This operation returns a mutable pointer to the trait. If the entity + * did not yet have the trait, it will be added. If a base entity had + * the trait, it will be overridden, and the value of the base trait + * will be copied to the entity before this function returns. + * + * @tparam T The trait to get. + * @param component The component for which to get the trait. + * @param is_added If provided, this parameter will be set to true if the trait was added. + * @return Pointer to the trait value. + */ + template + T* get_trait_mut(flecs::entity component, bool *is_added = nullptr) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_mut_w_entity( + m_world, m_id, ecs_trait( + _::component_info::id(m_world), + component.id()), + is_added)); + } + + /** Get mutable trait tag value. + * This operation returns a mutable pointer to the trait. If the entity + * did not yet have the trait, it will be added. If a base entity had + * the trait, it will be overridden, and the value of the base trait + * will be copied to the entity before this function returns. + * + * The trait passed to the function should not be a component. + * + * @tparam C The component for which to get the trait. + * @param trait The trait to get. + * @param is_added If provided, this parameter will be set to true if the trait was added. + * @return Pointer to the trait value. + */ + template + C* get_trait_tag_mut(flecs::entity trait, bool *is_added = nullptr) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_mut_w_entity( + m_world, m_id, ecs_trait( + _::component_info::id(m_world), + trait.id()), + is_added)); + } + + /** Signal that component was modified. + * + * @tparam T component that was modified. + */ + template + void modified() const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + ecs_modified_w_entity(m_world, m_id, _::component_info::id(m_world)); + } + + /** Signal that component was modified. + * + * @param component component that was modified. + */ + void modified(flecs::entity component) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_modified_w_entity(m_world, m_id, component.id()); + } + + /** Signal that component was modified. + * + * @param component id of component that was modified. + */ + void modified(entity_t component) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_modified_w_entity(m_world, m_id, component); + } + + /** Get reference to component. + * A reference allows for quick and safe access to a component value, and is + * a faster alternative to repeatedly calling 'get' for the same component. + * + * @tparam T component for which to get a reference. + * @return The reference. + */ + template + ref get_ref() const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(_::component_info::size() != 0, + ECS_INVALID_PARAMETER, NULL); + return ref(m_world, m_id); + } + + /** Get parent from an entity. + * This operation retrieves the parent entity that has the specified + * component. If no parent with the specified component is found, an entity + * with id 0 is returned. If multiple parents have the specified component, + * the operation returns the first encountered one. + * + * @tparam T The component for which to find the parent. + * @return The parent entity. + */ + template + flecs::entity get_parent() { + return flecs::entity(m_world, ecs_get_parent_w_entity(m_world, m_id, + _::component_info::id(m_world))); + } + + flecs::entity get_parent(flecs::entity e) { + return flecs::entity(m_world, ecs_get_parent_w_entity(m_world, m_id, e.id())); + } + + /** Clear an entity. + * This operation removes all components from an entity without recycling + * the entity id. + */ + void clear() const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_clear(m_world, m_id); + } + + /** Delete an entity. + * Entities have to be deleted explicitly, and are not deleted when the + * flecs::entity object goes out of scope. + */ + void destruct() const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_delete(m_world, m_id); + } + + /** Lookup an entity by name. + * Lookup an entity in the scope of this entity. The provided path may + * contain double colons as scope separators, for example: "Foo::Bar". + * + * @param path The name of the entity to lookup. + * @return The found entity, or entity::null if no entity matched. + */ + entity lookup(const char *path) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + auto id = ecs_lookup_path_w_sep(m_world, m_id, path, "::", "::"); + return entity(m_world, id); + } + + /** Check if entity has the provided entity. + * + * @param entity The entity id to check. + * @return True if the entity has the provided entity id, false otherwise. + */ + bool has(entity_t e) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, e); + } + + /** Check if entity has the provided parent. + * + * @param parent The parent id to check. + * @return True if the entity has the provided parent id, false otherwise. + */ + bool has_childof(entity_t parent) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, ECS_CHILDOF | parent); + } + + /** Check if entity has the provided base. + * + * @param base The entity id to check. + * @return True if the entity has the provided base id, false otherwise. + */ + bool has_instanceof(entity_t base) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, ECS_INSTANCEOF | base); + } + + /** Check if entity has the provided type. + * + * @param entity The type pointer to check. + * @return True if the entity has the provided type, false otherwise. + */ + bool has(type_t type) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_type(m_world, m_id, type); + } + + /** Check if entity has the provided entity. + * + * @param entity The entity to check. + * @return True if the entity has the provided entity, false otherwise. + */ + bool has(const entity& e) const { + return has(e.id()); + } + + /** Check if entity has the provided parent. + * + * @param parent The entity to check. + * @return True if the entity has the provided parent, false otherwise. + */ + bool has_childof(const entity& parent) const { + return has_childof(parent.id()); + } + + /** Check if entity has the provided base. + * + * @param base The entity to check. + * @return True if the entity has the provided base, false otherwise. + */ + bool has_instanceof(const entity& base) const { + return has_instanceof(base.id()); + } + + /** Check if entity has the provided component. + * + * @tparam T The component to check. + * @return True if the entity has the provided component, false otherwise. + */ + template + bool has() const { + return has(_::component_info::id(m_world)); + } + + /** Check if entity owns the provided entity id. + * An entity id is owned if it is not shared from a base entity. + * + * @param entity The entity id to check. + * @return True if the entity owns the provided entity id, false otherwise. + */ + bool owns(entity_t e) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_owns_entity(m_world, m_id, e, true); + } + + /** Check if entity owns the provided type. + * An type is owned if it is not shared from a base entity. + * + * @param type The type to check. + * @return True if the entity owns the provided type, false otherwise. + */ + bool owns(type_t type) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_type_owns_type(m_world, ecs_get_type(m_world, m_id), type, true); + } + + /** Check if entity owns the provided entity. + * An entity is owned if it is not shared from a base entity. + * + * @param entity The entity to check. + * @return True if the entity owns the provided entity, false otherwise. + */ + bool owns(const entity& e) const { + return owns(e.id()); + } + + /** Check if entity owns the provided component. + * An component is owned if it is not shared from a base entity. + * + * @tparam T The component to check. + * @return True if the entity owns the provided component, false otherwise. + */ + template + bool owns() const { + return owns(_::component_info::id(m_world)); + } + + /** Check if entity has the provided trait. + * + * @tparam T The trait to check. + * @tparam C The component for which to check the trait. + * @return True if the entity has the provided trait, false otherwise. + */ + template + bool has_trait() const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, ecs_trait( + _::component_info::id(m_world), + _::component_info::id(m_world))); + } + + /** Check if entity has the provided trait. + * + * @tparam T The trait to check. + * @param component The component for which to check the trait. + * @return True if the entity has the provided trait, false otherwise. + */ + template + bool has_trait(flecs::entity component) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, ecs_trait( + component.id(), _::component_info::id(m_world))); + } + + /** Check if entity has the provided trait tag. + * The provided trait tag should not be a component. + * + * @tparam C The component for which to check the trait tag. + * @param trait The trait tag to check. + * @return True if the entity has the provided trait tag, false otherwise. + */ + template + bool has_trait_tag(flecs::entity trait) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, ecs_trait( + _::component_info::id(m_world), trait.id())); + } + + /** Check if entity has the provided trait. + * The provided trait should not be a component. + * + * @param trait The trait to check. + * @param component The component for which to check the trait. + * @return True if the entity has the provided trait, false otherwise. + */ + bool has_trait(flecs::entity trait, flecs::entity e) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, ecs_trait( + e.id(), trait.id())); + } + + /** Check if entity has the provided switch. + * + * @param sw The switch to check. + * @return True if the entity has the provided switch, false otherwise. + */ + bool has_switch(flecs::type sw) const; + + /** Check if entity has the provided case id. + * + * @param sw_case The case id to check. + * @return True if the entity has the provided case, false otherwise. + */ + bool has_case(entity_t sw_case) const { + ecs_assert(m_world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_has_entity(m_world, m_id, flecs::Case | sw_case); + } + + /** Check if entity has the provided case. + * + * @param sw_case The case to check. + * @return True if the entity has the provided case, false otherwise. + */ + bool has_case(flecs::entity sw_case) const { + return this->has_case(sw_case.id()); + } + + template + bool has_case() const { + return this->has_case(_::component_info::id(m_world)); + } + + /** Get case for switch. + * + * @param sw The switch for which to obtain the case. + * @return True if the entity has the provided case, false otherwise. + */ + flecs::entity get_case(flecs::type sw) const; + + /** Test if component is enabled. + * + * @tparam T The component to test. + * @return True if the component is enabled, false if it has been disabled. + */ + template + bool is_enabled() { + return ecs_is_component_enabled_w_entity( + m_world, m_id, _::component_info::id(m_world)); + } + + /** Test if component is enabled. + * + * @param id The component to test. + * @return True if the component is enabled, false if it has been disabled. + */ + bool is_enabled(flecs::entity_t id) { + return ecs_is_component_enabled_w_entity( + m_world, m_id, id); + } + + /** Test if component is enabled. + * + * @param entity The component to test. + * @return True if the component is enabled, false if it has been disabled. + */ + bool is_enabled(const flecs::entity& e) { + return is_enabled(e.id()); + } + + /** Get current delta time. + * Convenience function so system implementations can get delta_time, even + * if they are using the .each() function. + * + * @return Current delta_time. + */ + FLECS_FLOAT delta_time() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->delta_time; + } + + /** Return iterator to entity children. + * Enables depth-first iteration over entity children. + * + * @return Iterator to child entities. + */ + child_iterator children() const; + + /** Used by builder class. Do not invoke. */ + template + void invoke(Func&& action) const { + action(m_world, m_id); + } + +protected: + + world_t *m_world; + entity_t m_id; +}; + +/** Prefab class */ +class prefab final : public entity { +public: + explicit prefab(const flecs::world& world, const char *name = nullptr) + : entity(world, name) + { + this->add(flecs::Prefab); + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// A collection of component ids used to describe the contents of a table +//////////////////////////////////////////////////////////////////////////////// + +class type final : entity { +public: + explicit type(const flecs::world& world, const char *name = nullptr, const char *expr = nullptr) + : entity(world, ecs_new_type(world.c_ptr(), 0, name, expr)) + { + sync_from_flecs(); + } + + type(const flecs::world& world, type_t t) + : entity( world.c_ptr(), 0 ) + , m_type( t ) + , m_normalized( t ) { } + + type(world_t *world, type_t t) + : entity( world, 0 ) + , m_type( t ) + , m_normalized( t ) { } + + type& add(const type& t) { + m_type = ecs_type_add(m_world, m_type, t.id()); + m_normalized = ecs_type_merge(m_world, m_normalized, t.c_ptr(), nullptr); + sync_from_me(); + return *this; + } + + type& add(const entity& e) { + m_type = ecs_type_add(m_world, m_type, e.id()); + m_normalized = ecs_type_add(m_world, m_normalized, e.id()); + sync_from_me(); + return *this; + } + + type& add_instanceof(const entity& e) { + m_type = ecs_type_add(m_world, m_type, e.id() | ECS_INSTANCEOF); + m_normalized = ecs_type_add(m_world, m_normalized, e.id() | ECS_INSTANCEOF); + sync_from_me(); + return *this; + } + + type& add_childof(const entity& e) { + m_type = ecs_type_add(m_world, m_type, e.id() | ECS_CHILDOF); + m_normalized = ecs_type_add(m_world, m_normalized, e.id() | ECS_CHILDOF); + sync_from_me(); + return *this; + } + + template + type& add_trait() { + m_type = ecs_type_add(m_world, m_type, + ecs_trait(_::component_info::id(m_world), + _::component_info::id(m_world))); + + m_normalized = ecs_type_add(m_world, m_normalized, + ecs_trait(_::component_info::id(m_world), + _::component_info::id(m_world))); + + sync_from_me(); + return *this; + } + + template + type& add_trait(flecs::entity component) { + m_type = ecs_type_add(m_world, m_type, + ecs_trait(component.id(), + _::component_info::id(m_world))); + + m_normalized = ecs_type_add(m_world, m_normalized, + ecs_trait(component.id(), + _::component_info::id(m_world))); + + sync_from_me(); + return *this; + } + + type& add_trait(flecs::entity trait, flecs::entity component) { + m_type = ecs_type_add(m_world, m_type, + ecs_trait(component.id(), trait.id())); + + m_normalized = ecs_type_add(m_world, m_normalized, + ecs_trait(component.id(), trait.id())); + + sync_from_me(); + return *this; + } + + template + type& add_trait_tag(flecs::entity trait) { + m_type = ecs_type_add(m_world, m_type, + ecs_trait(_::component_info::id(), trait.id())); + + m_normalized = ecs_type_add(m_world, m_normalized, + ecs_trait(_::component_info::id(), trait.id())); + + sync_from_me(); + return *this; + } + + template + type& add() { + std::stringstream str; + if (!_::pack_args_to_string(m_world, str)) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + std::string expr = str.str(); + ecs_type_t t = ecs_type_from_str(m_world, expr.c_str()); + m_type = ecs_type_merge(m_world, m_type, t, nullptr); + m_normalized = ecs_type_merge(m_world, m_normalized, t, nullptr); + sync_from_me(); + + return *this; + } + + std::string str() const { + char *str = ecs_type_str(m_world, m_type); + std::string result(str); + ecs_os_free(str); + return result; + } + + type_t c_ptr() const { + return m_type; + } + + // Expose entity id without making the entity class public. + entity_t id() const { + return m_id; + } + + type_t c_normalized() const { + return m_normalized; + } + + void enable() const { + ecs_assert(m_id != 0, ECS_INVALID_OPERATION, NULL); + ecs_enable(m_world, m_id, true); + } + + void disable() const { + ecs_assert(m_id != 0, ECS_INVALID_OPERATION, NULL); + ecs_enable(m_world, m_id, false); + } + + flecs::vector vector() { + return flecs::vector( (ecs_vector_t*)m_normalized ); + } + +private: + void sync_from_me() { + ecs_assert(m_id != 0, ECS_INVALID_OPERATION, NULL); + EcsType *tc = ecs_get_mut(m_world, m_id, EcsType, NULL); + if (tc) { + tc->type = m_type; + tc->normalized = m_normalized; + } + } + + void sync_from_flecs() { + ecs_assert(m_id != 0, ECS_INVALID_OPERATION, NULL); + EcsType *tc = ecs_get_mut(m_world, m_id, EcsType, NULL); + if (tc) { + m_type = tc->type; + m_normalized = tc->normalized; + } + } + + type_t m_type; + type_t m_normalized; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity range, allows for operating on a range of consecutive entities +//////////////////////////////////////////////////////////////////////////////// + +class entity_range final : public entity_builder { +public: + entity_range(const world& world, std::int32_t count) + : m_world(world.c_ptr()) + , m_ids( ecs_bulk_new_w_type(m_world, nullptr, count)) + , m_count(count) { } + + entity_range(const world& world, std::int32_t count, flecs::type type) + : m_world(world.c_ptr()) + , m_ids( ecs_bulk_new_w_type(m_world, type.c_ptr(), count)) + , m_count(count) { } + + template + void invoke(Func&& action) const { + for (int i = 0; i < m_count; i ++) { + action(m_world, m_ids[i]); + } + } + +private: + world_t *m_world; + const entity_t *m_ids; + std::int32_t m_count; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Register component, provide global access to component handles / metadata +//////////////////////////////////////////////////////////////////////////////// + +namespace _ +{ + // Trick to obtain typename from type, as described here + // https://blog.molecular-matters.com/2015/12/11/getting-the-type-of-a-template-argument-as-string-without-rtti/ + // + // The code from the link has been modified to work with more types, and across + // multiple compilers. + // + struct name_util { + /* Remove parts from typename that aren't needed for component name */ + static void trim_name(char *typeName) { + ecs_size_t len = ecs_os_strlen(typeName); + + /* Remove 'const' */ + ecs_size_t const_len = ecs_os_strlen("const "); + if ((len > const_len) && !ecs_os_strncmp(typeName, "const ", const_len)) { + ecs_os_memmove(typeName, typeName + const_len, len - const_len); + typeName[len - const_len] = '\0'; + len -= const_len; + } + + /* Remove 'struct' */ + ecs_size_t struct_len = ecs_os_strlen("struct "); + if ((len > struct_len) && !ecs_os_strncmp(typeName, "struct ", struct_len)) { + ecs_os_memmove(typeName, typeName + struct_len, len - struct_len); + typeName[len - struct_len] = '\0'; + len -= struct_len; + } + + /* Remove 'class' */ + ecs_size_t class_len = ecs_os_strlen("class "); + if ((len > class_len) && !ecs_os_strncmp(typeName, "class ", class_len)) { + ecs_os_memmove(typeName, typeName + class_len, len - class_len); + typeName[len - class_len] = '\0'; + len -= class_len; + } + + while (typeName[len - 1] == ' ' || + typeName[len - 1] == '&' || + typeName[len - 1] == '*') + { + len --; + typeName[len] = '\0'; + } + + /* Remove const at end of string */ + if (len > const_len) { + if (!ecs_os_strncmp(&typeName[len - const_len], " const", const_len)) { + typeName[len - const_len] = '\0'; + } + } + } + }; + +// Compiler-specific conversion from __PRETTY_FUNCTION__ to component name. +// This code uses a trick that instantiates a function for the component type. +// Then __PRETTY_FUNCTION__ is used to obtain the name of the function. Because +// the result of __PRETTY_FUNCTION__ is not standardized, there are different +// implementations for clang, gcc and msvc. Code that uses a different compiler +// needs to register component names explicitly. +#if defined(__clang__) + static const unsigned int FRONT_SIZE = sizeof("static const char* flecs::_::name_helper<") - 1u; + static const unsigned int BACK_SIZE = sizeof(">::name() [T = ]") - 1u; + + template + struct name_helper + { + static const char* name(void) { + static const size_t size = (sizeof(__PRETTY_FUNCTION__) - FRONT_SIZE - BACK_SIZE) / 2 + 1u; + static char typeName[size + 6] = {}; + memcpy(typeName, __PRETTY_FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#elif defined(__GNUC__) + static const unsigned int FRONT_SIZE = sizeof("static const char* flecs::_::name_helper::name() [with T = ") - 1u; + static const unsigned int BACK_SIZE = sizeof("]") - 1u; + + template + struct name_helper + { + static const char* name(void) { + static const size_t size = sizeof(__PRETTY_FUNCTION__) - FRONT_SIZE - BACK_SIZE; + static char typeName[size + 6] = {}; + memcpy(typeName, __PRETTY_FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#elif defined(_WIN32) + static const unsigned int FRONT_SIZE = sizeof("flecs::_::name_helper<") - 1u; + static const unsigned int BACK_SIZE = sizeof(">::name") - 1u; + + template + struct name_helper + { + static const char* name(void) { + static const size_t size = sizeof(__FUNCTION__) - FRONT_SIZE - BACK_SIZE; + static char typeName[size + 6] = {}; + memcpy(typeName, __FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#elif +#error "implicit component registration not supported" +#endif + +// The following functions are lifecycle callbacks that are automatically +// registered with flecs to ensure component lifecycle is handled correctly. Not +// all types require this, yet callbacks are registered by default, which +// introduces some overhead when working with components. +// +// An application can optimize this by explicitly registering a component as a +// plain old datatype, with world.pod_component(). + +template +void component_ctor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)entity_ptr; + (void)size; + (void)ctx; + + ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *t_ptr = static_cast(ptr); + + for (int i = 0; i < count; i ++) { + new(&t_ptr[i]) T; + } +} + +template +void component_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)entity_ptr; + (void)size; + (void)ctx; + + ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *t_ptr = static_cast(ptr); + + for (int i = 0; i < count; i ++) { + t_ptr[i].~T(); + } +} + +template +void component_copy( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + const void *src_ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)dst_entity; + (void)src_entity; + (void)size; + (void)ctx; + + ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *t_dst_ptr = static_cast(dst_ptr); + const T *t_src_ptr = static_cast(src_ptr); + + for (int i = 0; i < count; i ++) { + t_dst_ptr[i] = t_src_ptr[i]; + } +} + +template +void component_move( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)dst_entity; + (void)src_entity; + (void)size; + (void)ctx; + + ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *t_dst_ptr = static_cast(dst_ptr); + T *t_src_ptr = static_cast(src_ptr); + + for (int i = 0; i < count; i ++) { + t_dst_ptr[i] = std::move(t_src_ptr[i]); + } +} + +// Register component lifecycle callbacks with flecs. +template +void register_lifecycle_actions( + ecs_world_t *world, + ecs_entity_t component, + bool ctor, + bool dtor, + bool copy, + bool move) +{ + if (!ecs_component_has_actions(world, component)) { + EcsComponentLifecycle cl{}; + if (ctor) { + cl.ctor = _::component_ctor< + typename std::remove_const< + typename std::remove_pointer::type>::type>; + } + if (dtor) { + cl.dtor = _::component_dtor< + typename std::remove_const< + typename std::remove_pointer::type>::type>; + } + if (copy) { + cl.copy = _::component_copy< + typename std::remove_const< + typename std::remove_pointer::type>::type>; + } + if (move) { + cl.move = _::component_move< + typename std::remove_const< + typename std::remove_pointer::type>::type>; + } + + ecs_set_component_actions_w_entity( world, component, &cl); + } +} + +// Class that holds component identifier and name. +// This class is used by most component operations to obtain the component id in +// a transparent way for the application. The component id is stored in a static +// member of a class, and is shared by all worlds in an application. As such, +// each world in an application must (and will) have the same id. +// +// Because of how global (templated) variables are instantiated, it is possible +// that different instances for the same component exist across different +// translation units. This is handled transparently by flecs. When a component +// id is requested from the component_info class, but the id is uninitialized, a +// lookup by name will be performed for the component on the world, which will +// return the id with which the component was already registered. This means +// component identifiers are eventually consistent across translation units. +// +// When a component id is requested for a world that had not yet registered the +// component, it will be registered with the existing identifier. This ensures +// that multiple worlds in the same application use the same component ids. +// +// There are a few limitations of this approach. +// +// 1) When two worlds register components in different orders, it is possible +// that different components receive the same identifier. When a world +// attempts to re-register a component with a different identifier, an error +// will be thrown. To prevent this from happening, worlds should register +// components in the same order. +// +// 2) Component names are used to ensure that different translation units use +// the same identifiers. If a component is registered under different names +// in the same application, id conflicts can occur. +// +// Known issues: +// +// It seems like component registration does not always work correctly in Unreal +// engine when recreating a world. A plausible cause for this is the hot +// reloading of dynamic libraries by the engine. A workaround for this issue is +// to call flecs::_::component_info::reset() before recreating the world. +// This will reset the global component state and avoids conflicts. The exact +// cause of the issue is investigated here: +// https://github.com/SanderMertens/flecs/issues/293 + +template +class component_info final { +public: + // Initialize component identifier + static void init(world_t* world, entity_t entity, bool allow_tag = true) { + // If an identifier was already set, check for consistency + if (s_id) { + // If an identifier was registered, a name should've been registered + // as well. + ecs_assert(s_name.c_str() != nullptr, ECS_INTERNAL_ERROR, NULL); + + // A component cannot be registered using a different identifier. + ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, + _::name_helper::name()); + + // Ensure the entity has the same name as what was registered. + if (s_id >= EcsFirstUserComponentId) { + char *path = ecs_get_fullpath(world, entity); + ecs_assert(!strcmp(path, s_name.c_str()), + ECS_INCONSISTENT_COMPONENT_NAME, + _::name_helper::name()); + ecs_os_free(path); + } + + // Component was already registered and data is consistent with new + // identifier, so nothing else to be done. + return; + } + + // Component wasn't registered yet, set the values. Register component + // name as the fully qualified flecs path. + char *path = ecs_get_fullpath(world, entity); + s_id = entity; + s_name = path; + s_allow_tag = allow_tag; + + // s_name is an std::string, so it will have made a copy + ecs_os_free(path); + } + + // Obtain a component identifier without registering lifecycle callbacks. + static entity_t id_no_lifecycle(world_t *world = nullptr, + const char *name = nullptr, bool allow_tag = true) + { + // If no id has been registered yet, do it now. + if (!s_id) { + if (!name) { + // If no name was provided, retrieve the name implicitly from + // the name_helper class. + name = _::name_helper::name(); + } + + s_allow_tag = allow_tag; + + // If no world was provided, we can't implicitly register the + // component. While there are a few cases where calling this + // function without a world is OK, in general functions should + // always provide a world to enable implicit registration. + ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); + + // Create entity with name first. This ensurs that the component is + // created in the right location in the hierarchy, in case a scoped + // name is used. This allows registering components with the same + // name in different namespaces. + // + // If the component was already registered for this world, this will + // resolve the existing component identifier. This enables + // transparent component registeration across multiple translation + // units, as long as the same world is used. + // + // The last parameter ('true') ensures that when the component was + // not yet registered, a new component identifier is created. + // Component identifiers and entity identifiers are equivalent, + // though a pool of identifiers at the start of the id range is + // reserved for components. This is a performance optimization, as + // low ids in some parts of the code allow for direct indexing. + flecs::world w(world); + flecs::entity result = entity(w, name, true); + + // Now use the resulting identifier to register the component. Note + // that the name is not passed into this function, as the entity was + // already created with the correct name. + ecs_entity_t entity = ecs_new_component( + world, result.id(), nullptr, + size(), + alignment()); + + // The identifier returned by the function should be the same as the + // identifier that was passed in. + ecs_assert(entity == result.id(), ECS_INTERNAL_ERROR, NULL); + + // Init the component_info instance with the identiifer. + init(world, entity); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Obtain a component identifier, register lifecycle callbacks if this is + // the first time the component is used. + static entity_t id(world_t *world = nullptr, const char *name = nullptr, + bool allow_tag = true) + { + // If no id has been registered yet, do it now. + if (!s_id) { + // This will register a component id, but will not register + // lifecycle callbacks. + id_no_lifecycle(world, name, allow_tag); + + // Register lifecycle callbacks, but only if the component has a + // size. Components that don't have a size are tags, and tags don't + // require construction/destruction/copy/move's. */ + if (size()) { + register_lifecycle_actions(world, s_id, + true, true, true, true); + } + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Obtain a component name + static const char* name(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // If the id is set, the name should also have been set + return s_name.c_str(); + } + + // Obtain a component name, don't register lifecycle if the component hadn't + // been registered yet. While functionally the same could be achieved by + // first calling id_no_lifecycle() and then name(), this function ensures + // that the lifecycle callback templates are not instantiated. This allows + // some types (such as module classes) to be created without a default + // constructor. + static const char* name_no_lifecycle(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id_no_lifecycle(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // Return + return s_name.c_str(); + } + + // Return the type of a component. + // The type is a vector of component ids. This will return a type with just + // the current component id. + static type_t type(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // Create a type from the component id. + if (!s_type) { + s_type = ecs_type_from_entity(world, s_id); + } + + ecs_assert(s_type != nullptr, ECS_INTERNAL_ERROR, NULL); + + return s_type; + } + + // Return the size of a component. + static size_t size() { + // C++ types that have no members still have a size. Use std::is_empty + // to check if the type is empty. If so, use 0 for the component size. + // + // If s_allow_tag is set to false, the size returned by C++ is used. + // This is useful in cases where class instances are still required, as + // is the case with module classes. + if (s_allow_tag && std::is_empty::value) { + return 0; + } else { + return sizeof(typename std::remove_pointer::type); + } + } + + // Return the alignment of a component. + static size_t alignment() { + if (size() == 0) { + return 0; + } else { + return alignof(typename std::remove_pointer::type); + } + } + + // Was the component already registered. + static bool registered() { + return s_id != 0; + } + + // This function is only used to test cross-translation unit features. No + // code other than test cases should invoke this function. + static void reset() { + s_id = 0; + s_type = NULL; + s_name.clear(); + } + +private: + static entity_t s_id; + static type_t s_type; + static std::string s_name; + static bool s_allow_tag; +}; + +// Global templated variables that hold component identifier and other info +template entity_t component_info::s_id( 0 ); +template type_t component_info::s_type( nullptr ); +template std::string component_info::s_name(""); +template bool component_info::s_allow_tag( true ); + + +//////////////////////////////////////////////////////////////////////////////// +//// Register a component with flecs +//////////////////////////////////////////////////////////////////////////////// + +} // namespace _ + +/** Plain old datatype, no lifecycle actions are registered */ +template +flecs::entity pod_component(const flecs::world& world, const char *name = nullptr, bool allow_tag = true) { + if (!name) { + name = _::name_helper::name(); + } + + world_t *world_ptr = world.c_ptr(); + entity_t id = 0; + + if (_::component_info::registered()) { + /* To support components across multiple worlds, ensure that the + * component ids are the same. */ + id = _::component_info::id_no_lifecycle(world_ptr, name, allow_tag); + + /* If entity is not empty check if the name matches */ + if (ecs_get_type(world_ptr, id) != nullptr) { + if (id >= EcsFirstUserComponentId) { + char *path = ecs_get_fullpath(world_ptr, id); + ecs_assert(!strcmp(path, name), + ECS_INCONSISTENT_COMPONENT_NAME, name); + ecs_os_free(path); + } + } + + /* Register name with entity, so that when the entity is created the + * correct id will be resolved from the name. */ + ecs_add_path_w_sep(world_ptr, id, 0, name, "::", "::"); + + /* If a component was already registered with this id but with a + * different size, the ecs_new_component function will fail. */ + + /* We need to explicitly call ecs_new_component here again. Even though + * the component was already registered, it may have been registered + * with a different world. This ensures that the component is registered + * with the same id for the current world. + * If the component was registered already, nothing will change. */ + ecs_entity_t entity = ecs_new_component( + world.c_ptr(), id, nullptr, + _::component_info::size(), + _::component_info::alignment()); + + (void)entity; + + ecs_assert(entity == id, ECS_INTERNAL_ERROR, NULL); + + /* This functionality could have been put in id_no_lifecycle, but since + * this code happens when a component is registered, and the entire API + * calls id_no_lifecycle, this would add a lot of overhead to each call. + * This is why when using multiple worlds, components should be + * registered explicitly. */ + } else { + /* If the component is not yet registered, ensure no other component + * or entity has been registered with this name */ + ecs_entity_t entity = ecs_lookup_fullpath(world_ptr, name); + + (void)entity; + + ecs_assert(entity == 0, ECS_COMPONENT_NAME_IN_USE, name); + + id = _::component_info::id_no_lifecycle(world_ptr, name, allow_tag); + } + + _::component_info::init(world_ptr, id, allow_tag); + _::component_info::init(world_ptr, id, allow_tag); + _::component_info::init(world_ptr, id, allow_tag); + _::component_info::init(world_ptr, id, allow_tag); + + return world.entity(id); +} + +/** Regular component with ctor, dtor copy and move actions */ +template +flecs::entity component(const flecs::world& world, const char *name = nullptr) { + flecs::entity result = pod_component(world, name); + + if (_::component_info::size()) { + _::register_lifecycle_actions(world.c_ptr(), result.id(), + true, true, true, true); + } + + return result; +} + +/** Trivially relocatable component that can be memcpy'd. */ +template +flecs::entity relocatable_component(const flecs::world& world, const char *name = nullptr) { + flecs::entity result = pod_component(world, name); + + _::register_lifecycle_actions(world.c_ptr(), result.id(), + true, true, true, false); + + return result; +} + +template +flecs::entity_t type_id() { + return _::component_info::id(); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Define a module +//////////////////////////////////////////////////////////////////////////////// + +template +flecs::entity module(const flecs::world& world, const char *name = nullptr) { + ecs_set_scope(world.c_ptr(), 0); + flecs::entity result = pod_component(world, name, false); + ecs_set_scope(world.c_ptr(), result.id()); + return result; +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Import a module +//////////////////////////////////////////////////////////////////////////////// + +template +flecs::entity import(world& world) { + if (!_::component_info::registered()) { + ecs_trace_1("import %s", _::name_helper::name()); + ecs_log_push(); + + ecs_entity_t scope = ecs_get_scope(world.c_ptr()); + + // Allocate module, so the this ptr will remain stable + T *module_data = new T(world); + + ecs_set_scope(world.c_ptr(), scope); + + flecs::entity m = world.lookup(_::component_info::name_no_lifecycle(world.c_ptr())); + + ecs_set_ptr_w_entity( + world.c_ptr(), + m.id(), + _::component_info::id_no_lifecycle(world.c_ptr()), + _::component_info::size(), + module_data); + + ecs_log_pop(); + + return m; + } else { + return flecs::entity(world, + _::component_info::id_no_lifecycle(world.c_ptr())); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +//// A filter is used to match subsets of tables +//////////////////////////////////////////////////////////////////////////////// + +class filter { +public: + filter() + : m_world( nullptr ) + , m_filter{ } {} + + explicit filter(const world& world) + : m_world( world.c_ptr() ) + , m_filter{ } { } + + filter& include(type type) { + m_filter.include = ecs_type_merge(m_world, m_filter.include, type.c_ptr(), nullptr); + return *this; + } + + filter& include(entity entity) { + m_filter.include = ecs_type_add(m_world, m_filter.include, entity.id()); + return *this; + } + + template + filter& include() { + m_filter.include = ecs_type_add(m_world, m_filter.include, _::component_info::id(m_world)); + return *this; + } + + filter& include_kind(match_kind kind) { + m_filter.include_kind = static_cast(kind); + return *this; + } + + type include() { + return type(m_world, m_filter.include); + } + + filter& exclude(type type) { + m_filter.exclude = ecs_type_merge(m_world, m_filter.exclude, type.c_ptr(), nullptr); + return *this; + } + + filter& exclude(entity entity) { + m_filter.exclude = ecs_type_add(m_world, m_filter.exclude, entity.id()); + return *this; + } + + template + filter& exclude() { + m_filter.exclude = ecs_type_add(m_world, m_filter.exclude, _::component_info::id(m_world)); + return *this; + } + + filter& exclude_kind(match_kind kind) { + m_filter.exclude_kind = static_cast(kind); + return *this; + } + + type exclude() { + return type(m_world, m_filter.exclude); + } + + const filter_t* c_ptr() const { + if (m_world) { + return &m_filter; + } else { + return nullptr; + } + } + +private: + world_t *m_world; + filter_t m_filter; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility to convert template argument pack to array of columns +//////////////////////////////////////////////////////////////////////////////// + +namespace _ { + +template +class column_args { +public: + struct Column { + void *ptr; + bool is_shared; + }; + + using Columns = std::array; + + column_args(ecs_iter_t* iter) { + populate_columns(iter, 0, (typename std::remove_reference::type>::type*)nullptr...); + } + + Columns m_columns; + +private: + /* Dummy function when last component has been added */ + void populate_columns(ecs_iter_t *iter, size_t index) { + (void)iter; + (void)index; + } + + /* Populate columns array recursively */ + template + void populate_columns(ecs_iter_t *iter, size_t index, T comp, Targs... comps) { + int32_t column = static_cast(index + 1); + void *ptr = ecs_column_w_size(iter, sizeof(*comp), column); + m_columns[index].ptr = ptr; + m_columns[index].is_shared = !ecs_is_owned(iter, column) && ptr != nullptr; + populate_columns(iter, index + 1, comps ...); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system each +//////////////////////////////////////////////////////////////////////////////// + +template +class each_invoker { + using Columns = typename column_args::Columns; + +public: + explicit each_invoker(Func func) : m_func(func) { } + + // Invoke system + template ::type* = nullptr> + static void call_system(ecs_iter_t *iter, Func func, size_t index, Columns& columns, Targs... comps) { + flecs::iter iter_wrapper(iter); + (void)index; + (void)columns; + + // Use any_column so we can transparently use shared components + for (auto row : iter_wrapper) { + func(iter_wrapper.entity(row), (_::any_column::type>( + (typename std::remove_reference< typename std::remove_pointer::type >::type*)comps.ptr, + static_cast(iter->count), comps.is_shared))[row]...); + } + } + + // Add components one by one to parameter pack + template ::type* = nullptr> + static void call_system(ecs_iter_t *iter, Func func, size_t index, Columns& columns, Targs... comps) { + call_system(iter, func, index + 1, columns, comps..., columns[index]); + } + + // Callback provided to flecs system + static void run(ecs_iter_t *iter) { + const Context *ctx = ecs_get(iter->world, iter->system, EcsContext); + each_invoker *self = (each_invoker*)ctx->ctx; + Func func = self->m_func; + column_args columns(iter); + call_system(iter, func, 0, columns.m_columns); + } + +private: + Func m_func; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system action +//////////////////////////////////////////////////////////////////////////////// + +template +class action_invoker { + using Columns = typename column_args::Columns; + +public: + explicit action_invoker(Func func) + : m_func(func) { } + + /* Invoke system */ + template ::type* = nullptr> + static void call_system(ecs_iter_t *iter, Func func, int index, Columns& columns, Targs... comps) { + (void)index; + (void)columns; + + flecs::iter iter_wrapper(iter); + + func(iter_wrapper, (column::type >::type>( + (typename std::remove_reference< typename std::remove_pointer::type >::type*)comps.ptr, iter->count, comps.is_shared))...); + } + + /** Add components one by one to parameter pack */ + template ::type* = nullptr> + static void call_system(ecs_iter_t *iter, Func func, int index, Columns& columns, Targs... comps) { + call_system(iter, func, index + 1, columns, comps..., columns[index]); + } + + /** Callback provided to flecs */ + static void run(ecs_iter_t *iter) { + const Context *ctx = ecs_get(iter->world, iter->system, EcsContext); + action_invoker *self = (action_invoker*)ctx->ctx; + Func func = self->m_func; + column_args columns(iter); + call_system(iter, func, 0, columns.m_columns); + } + +private: + Func m_func; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system iterate action +//////////////////////////////////////////////////////////////////////////////// + +template +class iter_invoker { + using Columns = typename column_args::Columns; + +public: + explicit iter_invoker(Func func) + : m_func(func) { } + + /* Invoke system */ + template ::type* = nullptr> + static void call_system(ecs_iter_t *iter, Func func, size_t index, Columns& columns, Targs... comps) { + (void)index; + (void)columns; + flecs::iter iter_wrapper(iter); + func(iter_wrapper, ((typename std::remove_reference< typename std::remove_pointer::type >::type*)comps.ptr)...); + } + + /** Add components one by one to parameter pack */ + template ::type* = nullptr> + static void call_system(ecs_iter_t *iter, Func func, size_t index, Columns& columns, Targs... comps) { + call_system(iter, func, index + 1, columns, comps..., columns[index]); + } + + /** Callback provided to flecs */ + static void run(ecs_iter_t *iter) { + const Context *ctx = ecs_get(iter->world, iter->system, EcsContext); + iter_invoker *self = (iter_invoker*)ctx->ctx; + Func func = self->m_func; + column_args columns(iter); + call_system(iter, func, 0, columns.m_columns); + } + +private: + Func m_func; +}; + +} // namespace _ + + +//////////////////////////////////////////////////////////////////////////////// +//// Persistent queries +//////////////////////////////////////////////////////////////////////////////// + +class query_base { +public: + /** Get pointer to C query object. + */ + query_t* c_ptr() const { + return m_query; + } + + /** Sort the output of a query. + * This enables sorting of entities across matched tables. As a result of this + * operation, the order of entities in the matched tables may be changed. + * Resorting happens when a query iterator is obtained, and only if the table + * data has changed. + * + * If multiple queries that match the same (sub)set of tables specify different + * sorting functions, resorting is likely to happen every time an iterator is + * obtained, which can significantly slow down iterations. + * + * The sorting function will be applied to the specified component. Resorting + * only happens if that component has changed, or when the entity order in the + * table has changed. If no component is provided, resorting only happens when + * the entity order changes. + * + * @tparam T The component used to sort. + * @param compare The compare function used to sort the components. + */ + template + void order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + ecs_query_order_by(m_world, m_query, + flecs::_::component_info::id(m_world), + (ecs_compare_action_t)compare); + } + + /** Sort the output of a query. + * Same as order_by, but with component identifier. + * + * @param component The component used to sort. + * @param compare The compare function used to sort the components. + */ + void order_by(flecs::entity component, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { + ecs_query_order_by(m_world, m_query, component.id(), compare); + } + + /** Group and sort matched tables. + * Similar yo ecs_query_order_by, but instead of sorting individual entities, this + * operation only sorts matched tables. This can be useful of a query needs to + * enforce a certain iteration order upon the tables it is iterating, for + * example by giving a certain component or tag a higher priority. + * + * The sorting function assigns a "rank" to each type, which is then used to + * sort the tables. Tables with higher ranks will appear later in the iteration. + * + * Resorting happens when a query iterator is obtained, and only if the set of + * matched tables for a query has changed. If table sorting is enabled together + * with entity sorting, table sorting takes precedence, and entities will be + * sorted within each set of tables that are assigned the same rank. + * + * @tparam T The component used to determine the group rank. + * @param rank The rank action. + */ + template + void group_by(int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + ecs_query_group_by(m_world, m_query, + flecs::_::component_info::id(m_world), rank); + } + + /** Group and sort matched tables. + * Same as group_by, but with component identifier. + * + * @param component The component used to determine the group rank. + * @param rank The rank action. + */ + void group_by(flecs::entity component, int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + ecs_query_group_by(m_world, m_query, component.id(), rank); + } + + /** Returns whether the query data changed since the last iteration. + * This operation must be invoked before obtaining the iterator, as this will + * reset the changed state. The operation will return true after: + * - new entities have been matched with + * - matched entities were deleted + * - matched components were changed + * + * @return true if entities changed, otherwise false. + */ + bool changed() { + return ecs_query_changed(m_query); + } + + /** Returns whether query is orphaned. + * When the parent query of a subquery is deleted, it is left in an orphaned + * state. The only valid operation on an orphaned query is deleting it. Only + * subqueries can be orphaned. + * + * @return true if query is orphaned, otherwise false. + */ + bool orphaned() { + return ecs_query_orphaned(m_query); + } + + /** Free the query. + */ + void destruct() { + ecs_query_free(m_query); + m_world = nullptr; + m_query = nullptr; + } + +protected: + world_t *m_world; + query_t *m_query; +}; + +template +class query : public query_base { + using Columns = typename _::column_args::Columns; + +public: + query() { + m_world = nullptr; + m_query = nullptr; + } + + explicit query(const world& world) { + std::stringstream str; + if (!_::pack_args_to_string(world.c_ptr(), str, true)) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + m_world = world.c_ptr(); + m_query = ecs_query_new(world.c_ptr(), str.str().c_str()); + } + + explicit query(const world& world, query_base& parent) { + std::stringstream str; + if (!_::pack_args_to_string(world.c_ptr(), str, true)) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + m_world = world.c_ptr(); + m_query = ecs_subquery_new(world.c_ptr(), parent.c_ptr(), str.str().c_str()); + } + + explicit query(const world& world, const char *expr) { + std::stringstream str; + m_world = world.c_ptr(); + if (!_::pack_args_to_string(world.c_ptr(), str, true)) { + m_query = ecs_query_new(world.c_ptr(), expr); + } else { + str << "," << expr; + m_query = ecs_query_new(world.c_ptr(), str.str().c_str()); + } + } + + explicit query(const world& world, query_base& parent, const char *expr) { + std::stringstream str; + m_world = world.c_ptr(); + if (!_::pack_args_to_string(world.c_ptr(), str, true)) { + m_query = ecs_subquery_new(world.c_ptr(), parent.c_ptr(), expr); + } else { + str << "," << expr; + m_query = ecs_subquery_new(world.c_ptr(), parent.c_ptr(), str.str().c_str()); + } + } + + query_iterator begin() const; + + query_iterator end() const; + + template + void each(Func func) const { + ecs_iter_t it = ecs_query_iter(m_query); + + while (ecs_query_next(&it)) { + _::column_args columns(&it); + _::each_invoker ctx(func); + ctx.call_system(&it, func, 0, columns.m_columns); + } + } + + /* DEPRECATED */ + template + void action(Func func) const { + ecs_iter_t it = ecs_query_iter(m_query); + + while (ecs_query_next(&it)) { + _::column_args columns(&it); + _::action_invoker ctx(func); + ctx.call_system(&it, func, 0, columns.m_columns); + } + } + + template + void iter(Func func) const { + ecs_iter_t it = ecs_query_iter(m_query); + + while (ecs_query_next(&it)) { + _::column_args columns(&it); + _::iter_invoker ctx(func); + ctx.call_system(&it, func, 0, columns.m_columns); + } + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Snapshots make a copy of the world state that can be restored +//////////////////////////////////////////////////////////////////////////////// + +class snapshot final { +public: + explicit snapshot(const world& world) + : m_world( world ) + , m_snapshot( nullptr ) { } + + snapshot(const snapshot& obj) + : m_world( obj.m_world ) + { + ecs_iter_t it = ecs_snapshot_iter(obj.m_snapshot, nullptr); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_snapshot_next); + } + + snapshot(snapshot&& obj) + : m_world(obj.m_world) + , m_snapshot(obj.m_snapshot) + { + obj.m_snapshot = nullptr; + } + + snapshot& operator=(const snapshot& obj) { + ecs_assert(m_world.c_ptr() == obj.m_world.c_ptr(), ECS_INVALID_PARAMETER, NULL); + ecs_iter_t it = ecs_snapshot_iter(obj.m_snapshot, nullptr); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_snapshot_next); + return *this; + } + + snapshot& operator=(snapshot&& obj) { + ecs_assert(m_world.c_ptr() == obj.m_world.c_ptr(), ECS_INVALID_PARAMETER, NULL); + m_snapshot = obj.m_snapshot; + obj.m_snapshot = nullptr; + return *this; + } + + void take() { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + + m_snapshot = ecs_snapshot_take(m_world.c_ptr()); + } + + void take(flecs::filter filter) { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + + ecs_iter_t it = ecs_filter_iter(m_world.c_ptr(), filter.c_ptr()); + m_snapshot = ecs_snapshot_take_w_iter( + &it, ecs_filter_next); + } + + void restore() { + if (m_snapshot) { + ecs_snapshot_restore(m_world.c_ptr(), m_snapshot); + m_snapshot = nullptr; + } + } + + ~snapshot() { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + } + + snapshot_t* c_ptr() const { + return m_snapshot; + } + + snapshot_filter filter(const filter& filter); + + filter_iterator begin(); + + filter_iterator end(); +private: + const world& m_world; + snapshot_t *m_snapshot; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Fluent interface to run a system manually +//////////////////////////////////////////////////////////////////////////////// + +class system_runner_fluent { +public: + system_runner_fluent(world_t *world, entity_t id, FLECS_FLOAT delta_time, void *param) + : m_world(world) + , m_id(id) + , m_delta_time(delta_time) + , m_param(param) + , m_filter() + , m_offset(0) + , m_limit(0) { } + + system_runner_fluent& filter(filter filter) { + m_filter = filter; + return *this; + } + + system_runner_fluent& offset(std::int32_t offset) { + m_offset = offset; + return *this; + } + + system_runner_fluent& limit(std::int32_t limit) { + m_limit = limit; + return *this; + } + + ~system_runner_fluent() { + ecs_run_w_filter( + m_world, m_id, m_delta_time, m_offset, m_limit, m_filter.c_ptr(), m_param); + } +private: + world_t *m_world; + entity_t m_id; + FLECS_FLOAT m_delta_time; + void *m_param; + flecs::filter m_filter; + std::int32_t m_offset; + std::int32_t m_limit; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Register a system with Flecs +//////////////////////////////////////////////////////////////////////////////// + +template +class system final : public entity { +public: + explicit system(const flecs::world& world, const char *name = nullptr, const char *signature = nullptr) + : entity(world, name) + , m_kind(static_cast(OnUpdate)) + , m_signature(signature) + , m_interval(0.0) + , m_on_demand(false) + , m_hidden(false) + , m_finalized(false) { + ecs_assert(m_id != 0, ECS_INTERNAL_ERROR, NULL); + } + + explicit system(const flecs::world& world, flecs::entity id) + : entity(world, id.id()) + , m_finalized(true) { } + + system& signature(const char *signature) { + ecs_assert(!m_finalized, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!m_signature, ECS_INVALID_PARAMETER, NULL); + m_signature = signature; + return *this; + } + + system& kind(entity_t kind) { + ecs_assert(!m_finalized, ECS_INVALID_PARAMETER, NULL); + m_kind = static_cast(kind); + return *this; + } + + system& interval(FLECS_FLOAT interval) { + if (!m_finalized) { + m_interval = interval; + } else { + ecs_set_interval(m_world, m_id, interval); + } + return *this; + } + + FLECS_FLOAT interval() { + return ecs_get_interval(m_world, m_id); + } + + // DEPRECATED: use interval instead + system& period(FLECS_FLOAT period) { + return this->interval(period); + } + + system& on_demand() { + ecs_assert(!m_finalized, ECS_INVALID_PARAMETER, NULL); + m_on_demand = true; + return *this; + } + + system& hidden() { + ecs_assert(!m_finalized, ECS_INVALID_PARAMETER, NULL); + m_hidden = true; + return *this; + } + + void enable() { + ecs_enable(m_world, m_id, true); + } + + void disable() { + ecs_enable(m_world, m_id, false); + } + + void set_period(FLECS_FLOAT period) const { + ecs_set_interval(m_world, m_id, period); + } + + void set_context(void *ctx) const { + EcsContext ctx_value = { ctx }; + ecs_set_ptr(m_world, m_id, EcsContext, &ctx_value); + } + + void* get_context() const { + const EcsContext *ctx = ecs_get(m_world, m_id, EcsContext); + if (ctx) { + return (void*)ctx->ctx; + } else { + return NULL; + } + } + + system_runner_fluent run(FLECS_FLOAT delta_time = 0.0f, void *param = nullptr) const { + return system_runner_fluent(m_world, m_id, delta_time, param); + } + + /* DEPRECATED. Use iter instead. */ + template + system& action(Func func) { + ecs_assert(!m_finalized, ECS_INVALID_PARAMETER, NULL); + auto ctx = new _::action_invoker(func); + + create_system(_::action_invoker::run, false); + + EcsContext ctx_value = {ctx}; + ecs_set_ptr(m_world, m_id, EcsContext, &ctx_value); + + return *this; + } + + /* Iter (or each) is mandatory and always the last thing that + * is added in the fluent method chain. Create system signature from both + * template parameters and anything provided by the signature method. */ + template + system& iter(Func func) { + ecs_assert(!m_finalized, ECS_INVALID_PARAMETER, NULL); + auto ctx = new _::iter_invoker(func); + + create_system(_::iter_invoker::run, false); + + EcsContext ctx_value = {ctx}; + ecs_set_ptr(m_world, m_id, EcsContext, &ctx_value); + + return *this; + } + + /* Each is similar to action, but accepts a function that operates on a + * single entity */ + template + system& each(Func func) { + auto ctx = new _::each_invoker(func); + + create_system(_::each_invoker::run, true); + + EcsContext ctx_value = {ctx}; + ecs_set_ptr(m_world, m_id, EcsContext, &ctx_value); + + return *this; + } + + ~system() = default; +private: + template + entity_t create_system(Invoker invoker, bool is_each) { + ecs_assert(m_id != 0, ECS_INTERNAL_ERROR, NULL); + + entity_t e; + bool is_trigger = m_kind == flecs::OnAdd || m_kind == flecs::OnRemove; + + if (is_trigger) { + // Don't add ANY source to each function if this is a trigger + is_each = false; + } + + std::string signature = build_signature(is_each); + + if (!signature.length()) { + signature = "0"; + } + + if (is_trigger) { + e = ecs_new_trigger( + m_world, + m_id, + nullptr, + m_kind, + signature.c_str(), + invoker); + } else { + e = ecs_new_system( + m_world, + m_id, + nullptr, + m_kind, + signature.c_str(), + invoker); + } + + ecs_assert(e == m_id, ECS_INTERNAL_ERROR, NULL); + + if (m_interval != 0) { + ecs_set_interval(m_world, e, m_interval); + } + + m_finalized = true; + + return e; + } + + std::string build_signature(bool is_each) { + bool is_set = false; + + std::stringstream str; + if (_::pack_args_to_string(m_world, str, is_each)) { + is_set = true; + } + + if (m_signature) { + if (is_set) { + str << ","; + } + str << m_signature; + is_set = true; + } + + if (m_hidden) { + if (is_set) { + str << ","; + } + str << "SYSTEM:Hidden"; + is_set = true; + } + + if (m_on_demand) { + if (is_set) { + str << ","; + } + str << "SYSTEM:EcsOnDemand"; + is_set = true; + } + + return str.str(); + } + + ecs_entity_t m_kind; + const char *m_signature = nullptr; + FLECS_FLOAT m_interval; + bool m_on_demand; + bool m_hidden; + bool m_finalized; // After set to true, call no more fluent functions +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Persistent queries +//////////////////////////////////////////////////////////////////////////////// + +template +class query_iterator +{ +public: + query_iterator() + : m_has_next(false) + , m_iter{ } { } + + query_iterator(const query& query) + : m_iter( ecs_query_iter(query.c_ptr()) ) + { + m_has_next = ecs_query_next(&m_iter); + } + + bool operator!=(query_iterator const& other) const { + return m_has_next != other.m_has_next; + } + + flecs::iter const operator*() const { + return flecs::iter(&m_iter); + } + + query_iterator& operator++() { + m_has_next = ecs_query_next(&m_iter); + return *this; + } + +private: + bool m_has_next; + ecs_iter_t m_iter; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for iterating over tables that match a filter +//////////////////////////////////////////////////////////////////////////////// + +class filter_iterator +{ +public: + filter_iterator(ecs_iter_next_action_t action) + : m_world(nullptr) + , m_has_next(false) + , m_iter{ } + , m_action(action) { } + + filter_iterator(const world& world, const filter& filter, ecs_iter_next_action_t action) + : m_world( world.c_ptr() ) + , m_iter( ecs_filter_iter(m_world, filter.c_ptr()) ) + , m_action(action) + { + m_has_next = m_action(&m_iter); + } + + filter_iterator(const world& world, const snapshot& snapshot, const filter& filter, ecs_iter_next_action_t action) + : m_world( world.c_ptr() ) + , m_iter( ecs_snapshot_iter(snapshot.c_ptr(), filter.c_ptr()) ) + , m_action(action) + { + m_has_next = m_action(&m_iter); + } + + bool operator!=(filter_iterator const& other) const { + return m_has_next != other.m_has_next; + } + + flecs::iter const operator*() const { + return flecs::iter(&m_iter); + } + + filter_iterator& operator++() { + m_has_next = m_action(&m_iter); + return *this; + } + +private: + world_t *m_world; + bool m_has_next; + ecs_iter_t m_iter; + ecs_iter_next_action_t m_action; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Tree iterator +//////////////////////////////////////////////////////////////////////////////// + +class tree_iterator +{ +public: + tree_iterator() + : m_has_next(false) + , m_iter{ } { } + + tree_iterator(flecs::entity entity) + : m_iter( ecs_scope_iter(entity.world().c_ptr(), entity.id()) ) + { + m_has_next = ecs_scope_next(&m_iter); + } + + bool operator!=(tree_iterator const& other) const { + return m_has_next != other.m_has_next; + } + + flecs::iter const operator*() const { + return flecs::iter(&m_iter); + } + + tree_iterator& operator++() { + m_has_next = ecs_scope_next(&m_iter); + return *this; + } + +private: + bool m_has_next; + ecs_iter_t m_iter; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a world-based filter iterator +//////////////////////////////////////////////////////////////////////////////// + +class world_filter { +public: + world_filter(const world& world, const filter& filter) + : m_world( world ) + , m_filter( filter ) { } + + inline filter_iterator begin() const { + return filter_iterator(m_world, m_filter, ecs_filter_next); + } + + inline filter_iterator end() const { + return filter_iterator(ecs_filter_next); + } + +private: + const world& m_world; + const filter& m_filter; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a snapshot-based filter iterator +//////////////////////////////////////////////////////////////////////////////// + +class snapshot_filter { +public: + snapshot_filter(const world& world, const snapshot& snapshot, const filter& filter) + : m_world( world ) + , m_snapshot( snapshot ) + , m_filter( filter ) { } + + inline filter_iterator begin() const { + return filter_iterator(m_world, m_snapshot, m_filter, ecs_snapshot_next); + } + + inline filter_iterator end() const { + return filter_iterator(ecs_snapshot_next); + } + +private: + const world& m_world; + const snapshot& m_snapshot; + const filter& m_filter; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a child table iterator +//////////////////////////////////////////////////////////////////////////////// + +class child_iterator { +public: + child_iterator(const entity& entity) + : m_parent( entity ) { } + + inline tree_iterator begin() const { + return tree_iterator(m_parent); + } + + inline tree_iterator end() const { + return tree_iterator(); + } + +private: + const entity& m_parent; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Reader for world/snapshot serialization +//////////////////////////////////////////////////////////////////////////////// + +class reader final { +public: + explicit reader(world& world) { + m_reader = ecs_reader_init(world.c_ptr()); + } + + reader(world& world, snapshot& snapshot) { + (void)world; + ecs_iter_t it = ecs_snapshot_iter(snapshot.c_ptr(), nullptr); + m_reader = ecs_reader_init_w_iter(&it, ecs_snapshot_next); + } + + int32_t read(char *buffer, std::int64_t size) { + return ecs_reader_read(buffer, static_cast(size), &m_reader); + } + +private: + ecs_reader_t m_reader; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Writer for world deserialization +//////////////////////////////////////////////////////////////////////////////// + +class writer final { +public: + explicit writer(world& world) { + m_writer = ecs_writer_init(world.c_ptr()); + } + + int32_t write(const char *buffer, std::int64_t size) { + return ecs_writer_write(buffer, static_cast(size), &m_writer); + } + +private: + ecs_writer_t m_writer; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Filter fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +inline snapshot_filter snapshot::filter(const flecs::filter& filter) { + return snapshot_filter(m_world, *this, filter); +} + +inline filter_iterator snapshot::begin() { + return filter_iterator(m_world, *this, flecs::filter(m_world), ecs_snapshot_next); +} + +inline filter_iterator snapshot::end() { + return filter_iterator(ecs_snapshot_next); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Query fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +template +inline query_iterator query::begin() const { + return query_iterator(*this); +} + +template +inline query_iterator query::end() const { + return query_iterator(); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Cached ptr fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +template +flecs::entity ref::entity() const { + return flecs::entity(m_world, m_entity); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +inline flecs::type entity::type() const { + return flecs::type(m_world, ecs_get_type(m_world, m_id)); +} + +inline flecs::type entity::to_type() const { + ecs_type_t type = ecs_type_from_entity(m_world, m_id); + return flecs::type(m_world, type); +} + +inline child_iterator entity::children() const { + return flecs::child_iterator(*this); +} + +//////////////////////////////////////////////////////////////////////////////// +//// Entity fluent fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +template +inline typename entity_builder::base_type& entity_builder::add(const entity& entity) const { + return add(entity.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::add(type type) const { + return add(type.c_ptr()); +} + +template +template +inline typename entity_builder::base_type& entity_builder::add_trait(flecs::entity component) const { + return add_trait(_::component_info::id(), component.id()); +} + +template +template +inline typename entity_builder::base_type& entity_builder::add_trait_tag(flecs::entity trait) const { + return add_trait(trait.id(), _::component_info::id()); +} + +template +inline typename entity_builder::base_type& entity_builder::add_trait(flecs::entity trait, flecs::entity entity) const { + return add_trait(trait.id(), entity.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove(const entity& entity) const { + return remove(entity.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove(type type) const { + return remove(type.c_ptr()); +} + +template +template +inline typename entity_builder::base_type& entity_builder::remove_trait(flecs::entity component) const { + return remove_trait(_::component_info::id(), component.id()); +} + +template +template +inline typename entity_builder::base_type& entity_builder::remove_trait_tag(flecs::entity trait) const { + return remove_trait(trait.id(), _::component_info::id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove_trait(flecs::entity trait, flecs::entity entity) const { + return remove_trait(trait.id(), entity.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::add_childof(const entity& entity) const { + return add_childof(entity.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove_childof(const entity& entity) const { + return remove_childof(entity.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::add_instanceof(const entity& entity) const { + return add_instanceof(entity.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove_instanceof(const entity& entity) const { + return remove_instanceof(entity.id()); +} + +template +template +inline typename entity_builder::base_type& entity_builder::set_trait_tag(flecs::entity trait, const C& value) const +{ + static_cast(this)->invoke( + [trait, &value](world_t *world, entity_t id) { + ecs_set_ptr_w_entity(world, id, + ecs_trait(_::component_info::id(world), trait.id()), + sizeof(C), &value); + }); + return *static_cast(this); +} + +template +template +inline typename entity_builder::base_type& entity_builder::set_trait(const T& value, flecs::entity tag) const +{ + static_cast(this)->invoke( + [tag, &value](world_t *world, entity_t id) { + ecs_set_ptr_w_entity(world, id, + ecs_trait(tag.id(), _::component_info::id(world)), + sizeof(T), &value); + }); + return *static_cast(this); +} + +template +inline typename entity_builder::base_type& entity_builder::add_owned(flecs::type type) const { + return add_owned(type.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::add_switch(const entity& sw) const { + return add_switch(sw.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::add_switch(const type& sw) const { + return add_switch(sw.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove_switch(const entity& sw) const { + return remove_switch(sw.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove_switch(const type& sw) const { + return remove_switch(sw.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::add_case(const entity& sw_case) const { + return add_case(sw_case.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::remove_case(const entity& sw_case) const { + return remove_case(sw_case.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::enable(const entity& e) const { + return enable(e.id()); +} + +template +inline typename entity_builder::base_type& entity_builder::disable(const entity& e) const { + return disable(e.id()); +} + +inline bool entity::has_switch(flecs::type type) const { + return ecs_has_entity(m_world, m_id, flecs::Switch | type.id()); +} + +inline flecs::entity entity::get_case(flecs::type sw) const { + return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw.id())); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Iter fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +inline flecs::entity iter::system() const { + return flecs::entity(m_iter->world, m_iter->system); +} + +inline flecs::world iter::world() const { + return flecs::world(m_iter->world); +} + +inline flecs::entity iter::entity(size_t row) const { + ecs_assert(row < (size_t)m_iter->count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return flecs::entity(m_iter->world, m_iter->entities[row]); +} + +/* Obtain column source (0 if self) */ +inline flecs::entity iter::column_source(int32_t col) const { + return flecs::entity(m_iter->world, ecs_column_source(m_iter, col)); +} + +/* Obtain component/tag entity of column */ +inline flecs::entity iter::column_entity(int32_t col) const { + return flecs::entity(m_iter->world, ecs_column_entity(m_iter, col)); +} + +/* Obtain type of column */ +inline type iter::column_type(int32_t col) const { + return flecs::type(m_iter->world, ecs_column_type(m_iter, col)); +} + +/* Obtain type of table being iterated over */ +inline type iter::table_type() const { + return flecs::type(m_iter->world, ecs_iter_type(m_iter)); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// World fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +inline void world::delete_entities(flecs::filter filter) const { + ecs_bulk_delete(m_world, filter.c_ptr()); +} + +template +inline void world::add(flecs::filter filter) const { + ecs_bulk_add_remove_type( + m_world, _::component_info::type(m_world), nullptr, filter.c_ptr()); +} + +inline void world::add(flecs::type t) const { + ecs_bulk_add_remove_type(m_world, t.c_ptr(), nullptr, nullptr); +} + +inline void world::add(flecs::type t, flecs::filter filter) const { + ecs_bulk_add_remove_type(m_world, t.c_ptr(), nullptr, filter.c_ptr()); +} + +inline void world::add(class flecs::entity e) const { + ecs_bulk_add_remove_type(m_world, e.to_type().c_ptr(), nullptr, nullptr); +} + +inline void world::add(class flecs::entity e, flecs::filter filter) const { + ecs_bulk_add_remove_type(m_world, e.to_type().c_ptr(), nullptr, filter.c_ptr()); +} + +template +inline void world::remove(flecs::filter filter) const { + ecs_bulk_add_remove_type( + m_world, nullptr, _::component_info::type(m_world), filter.c_ptr()); +} + +inline void world::remove(flecs::type t) const { + ecs_bulk_add_remove_type(m_world, nullptr, t.c_ptr(), nullptr); +} + +inline void world::remove(flecs::type t, flecs::filter filter) const { + ecs_bulk_add_remove_type(m_world, nullptr, t.c_ptr(), filter.c_ptr()); +} + +inline void world::remove(class entity e) const { + ecs_bulk_add_remove_type(m_world, nullptr, e.to_type().c_ptr(), nullptr); +} + +inline void world::remove(class entity e, flecs::filter filter) const { + ecs_bulk_add_remove_type(m_world, nullptr, e.to_type().c_ptr(), filter.c_ptr()); +} + +inline flecs::world_filter world::filter(const flecs::filter& filter) const { + return flecs::world_filter(*this, filter); +} + +inline filter_iterator world::begin() const { + return filter_iterator(*this, flecs::filter(*this), ecs_filter_next); +} + +inline filter_iterator world::end() const { + return filter_iterator(ecs_filter_next); +} + +inline int world::count(flecs::filter filter) const { + return ecs_count_w_filter(m_world, filter.c_ptr()); +} + +inline void world::init_builtin_components() { + pod_component("flecs::core::Component"); + pod_component("flecs::core::Type"); + pod_component("flecs::core::Name"); +} + +template +inline flecs::entity world::use(const char *alias) { + entity_t id = _::component_info::id(m_world); + const char *name = alias; + if (!name) { + // If no name is defined, use the entity name without the scope + name = ecs_get_name(m_world, id); + } + ecs_use(m_world, id, name); + return flecs::entity(m_world, id); +} + +inline flecs::entity world::use(const char *name, const char *alias) { + entity_t id = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::"); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_use(m_world, id, alias); + return flecs::entity(m_world, id); +} + +inline void world::use(flecs::entity e, const char *alias) { + entity_t id = e.id(); + const char *name = alias; + if (!name) { + // If no name is defined, use the entity name without the scope + ecs_get_name(m_world, id); + } + ecs_use(m_world, id, alias); +} + +inline entity world::lookup(const char *name) const { + auto id = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::"); + return flecs::entity(*this, id); +} + +inline entity world::lookup(std::string& name) const { + auto id = ecs_lookup_path_w_sep(m_world, 0, name.c_str(), "::", "::"); + return flecs::entity(*this, id); +} + +template +void world::set(T value) const { + flecs::entity e(m_world, _::component_info::id(m_world)); + e.set(value); +} + +template +T* world::get_mut() const { + flecs::entity e(m_world, _::component_info::id(m_world)); + return e.get_mut(); +} + +template +void world::modified() const { + flecs::entity e(m_world, _::component_info::id(m_world)); + return e.modified(); +} + +template +void world::patch(std::function func) const { + flecs::entity e(m_world, _::component_info::id(m_world)); + e.patch(func); +} + +template +const T* world::get() const { + flecs::entity e(m_world, _::component_info::id(m_world)); + return e.get(); +} + +template +bool world::has() const { + flecs::entity e(m_world, _::component_info::id(m_world)); + return e.has(); +} + +template +void world::remove() const { + flecs::entity e(m_world, _::component_info::id(m_world)); + e.remove(); +} + +template +flecs::entity world::singleton() { + return flecs::entity(m_world, _::component_info::id(m_world)); +} + +template +inline flecs::entity world::entity(Args &&... args) const { + return flecs::entity(*this, std::forward(args)...); +} + +template +inline flecs::entity world::prefab(Args &&... args) const { + return flecs::prefab(*this, std::forward(args)...); +} + +template +inline flecs::type world::type(Args &&... args) const { + return flecs::type(*this, std::forward(args)...); +} + +template +inline flecs::system world::system(Args &&... args) const { + return flecs::system(*this, std::forward(args)...); +} + +template +inline flecs::query world::query(Args &&... args) const { + return flecs::query(*this, std::forward(args)...); +} + +template +inline flecs::entity world::module(Args &&... args) const { + return flecs::module(*this, std::forward(args)...); +} + +template +inline flecs::entity world::import() { + return flecs::import(*this); +} + +template +inline flecs::entity world::component(Args &&... args) const { + return flecs::component(*this, std::forward(args)...); +} + +template +inline flecs::entity world::pod_component(Args &&... args) const { + return flecs::pod_component(*this, std::forward(args)...); +} + +template +inline flecs::entity world::relocatable_component(Args &&... args) const { + return flecs::relocatable_component(*this, std::forward(args)...); +} + +template +inline flecs::snapshot world::snapshot(Args &&... args) const { + return flecs::snapshot(*this, std::forward(args)...); +} + +/** Utilities to convert type trait to flecs signature syntax */ + +namespace _ +{ + +template ::value == true, void>::type* = nullptr> +constexpr const char *inout_modifier() { + return "[in] "; +} + +template ::value == true, void>::type* = nullptr> +constexpr const char *inout_modifier() { + return "[out] "; +} + +template ::value == false && std::is_reference::value == false, void>::type* = nullptr> +constexpr const char *inout_modifier() { + return ""; +} + +template ::value == true, void>::type* = nullptr> +constexpr const char *optional_modifier() { + return "?"; +} + +template ::value == false, void>::type* = nullptr> +constexpr const char *optional_modifier() { + return ""; +} + +/** Convert template arguments to string */ +template +bool pack_args_to_string(world_t *world, std::stringstream& str, bool is_each) { + (void)world; + + std::array ids = { + (_::component_info::name(world))... + }; + + std::array inout_modifiers = { + (inout_modifier())... + }; + + std::array optional_modifiers = { + (optional_modifier())... + }; + + size_t i = 0; + for (auto id : ids) { + if (i) { + str << ","; + } + + str << inout_modifiers[i]; + str << optional_modifiers[i]; + + if (is_each) { + str << "ANY:"; + } + str << id; + i ++; + } + + return i != 0; +} + +} // namespace _ + +} // namespace flecs + +#endif +#endif + +#endif + +#endif