diff --git a/.gitmodules b/.gitmodules index fc014b5..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "libs/flecs"] - path = libs/flecs - url = https://github.com/SanderMertens/flecs diff --git a/build.zig.zon b/build.zig.zon index c3b93de..632fde8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,15 +2,11 @@ .name = "flecs-zig-ble", .version = "0.1.0", .paths = .{ + "libs/flecs", "src", "build.zig", "build.zig.zon", "README.md", "UNLICENSE.txt", - - // Flecs dependency. - "libs/flecs/flecs.c", - "libs/flecs/flecs.h", - "libs/flecs/LICENSE", }, } diff --git a/libs/flecs b/libs/flecs deleted file mode 160000 index 198607d..0000000 --- a/libs/flecs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 198607d10ab8f8fc44540043271d6e3be019250b diff --git a/libs/flecs/LICENSE b/libs/flecs/LICENSE new file mode 100644 index 0000000..8d7b5a0 --- /dev/null +++ b/libs/flecs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Sander Mertens + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/flecs/flecs.c b/libs/flecs/flecs.c new file mode 100644 index 0000000..4e91fdd --- /dev/null +++ b/libs/flecs/flecs.c @@ -0,0 +1,68068 @@ +/** + * @file bootstrap.c + * @brief Bootstrap entities in the flecs.core namespace. + * + * Before the ECS storage can be used, core entities such first need to be + * initialized. For example, components in Flecs are stored as entities in the + * ECS storage itself with an EcsComponent component, but before this component + * can be stored, the component itself needs to be initialized. + * + * The bootstrap code uses lower-level APIs to initialize the data structures. + * After bootstrap is completed, regular ECS operations can be used to create + * entities and components. + * + * The bootstrap file also includes several lifecycle hooks and observers for + * builtin features, such as relationship properties and hooks for keeping the + * entity name administration in sync with the (Identifier, Name) component. + */ + +#include "flecs.h" +/** + * @file private_api.h + * @brief Private functions. + */ + +#ifndef FLECS_PRIVATE_H +#define FLECS_PRIVATE_H + +/** + * @file private_types.h + * @brief Private types. + */ + +#ifndef FLECS_PRIVATE_TYPES_H +#define FLECS_PRIVATE_TYPES_H + +#ifndef __MACH__ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +#include +#include +#include + +/** + * @file datastructures/entity_index.h + * @brief Entity index data structure. + * + * The entity index stores the table, row for an entity id. + */ + +#ifndef FLECS_ENTITY_INDEX_H +#define FLECS_ENTITY_INDEX_H + +#define FLECS_ENTITY_PAGE_SIZE (1 << FLECS_ENTITY_PAGE_BITS) +#define FLECS_ENTITY_PAGE_MASK (FLECS_ENTITY_PAGE_SIZE - 1) + +typedef struct ecs_entity_index_page_t { + ecs_record_t records[FLECS_ENTITY_PAGE_SIZE]; +} ecs_entity_index_page_t; + +typedef struct ecs_entity_index_t { + ecs_vec_t dense; + ecs_vec_t pages; + int32_t alive_count; + uint64_t max_id; + ecs_block_allocator_t page_allocator; + ecs_allocator_t *allocator; +} ecs_entity_index_t; + +/** Initialize entity index. */ +void flecs_entity_index_init( + ecs_allocator_t *allocator, + ecs_entity_index_t *index); + +/** Deinitialize entity index. */ +void flecs_entity_index_fini( + ecs_entity_index_t *index); + +/* Get entity (must exist/must be alive) */ +ecs_record_t* flecs_entity_index_get( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Get entity (must exist/may not be alive) */ +ecs_record_t* flecs_entity_index_get_any( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Get entity (may not exist/must be alive) */ +ecs_record_t* flecs_entity_index_try_get( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Get entity (may not exist/may not be alive) */ +ecs_record_t* flecs_entity_index_try_get_any( + const ecs_entity_index_t *index, + uint64_t entity); + +/** Ensure entity exists. */ +ecs_record_t* flecs_entity_index_ensure( + ecs_entity_index_t *index, + uint64_t entity); + +/* Remove entity */ +void flecs_entity_index_remove( + ecs_entity_index_t *index, + uint64_t entity); + +/* Set generation of entity */ +void flecs_entity_index_set_generation( + ecs_entity_index_t *index, + uint64_t entity); + +/* Get current generation of entity */ +uint64_t flecs_entity_index_get_generation( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Return whether entity is alive */ +bool flecs_entity_index_is_alive( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Return whether entity is valid */ +bool flecs_entity_index_is_valid( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Return whether entity exists */ +bool flecs_entity_index_exists( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Create or recycle entity id */ +uint64_t flecs_entity_index_new_id( + ecs_entity_index_t *index); + +/* Bulk create or recycle new entity ids */ +uint64_t* flecs_entity_index_new_ids( + ecs_entity_index_t *index, + int32_t count); + +/* Set size of index */ +void flecs_entity_index_set_size( + ecs_entity_index_t *index, + int32_t size); + +/* Return number of entities in index */ +int32_t flecs_entity_index_count( + const ecs_entity_index_t *index); + +/* Return number of allocated entities in index */ +int32_t flecs_entity_index_size( + const ecs_entity_index_t *index); + +/* Return number of not alive entities in index */ +int32_t flecs_entity_index_not_alive_count( + const ecs_entity_index_t *index); + +/* Clear entity index */ +void flecs_entity_index_clear( + ecs_entity_index_t *index); + +/* Return number of alive entities in index */ +const uint64_t* flecs_entity_index_ids( + const ecs_entity_index_t *index); + +void flecs_entity_index_copy( + ecs_entity_index_t *dst, + const ecs_entity_index_t *src); + +void flecs_entity_index_restore( + ecs_entity_index_t *dst, + const ecs_entity_index_t *src); + +#define ecs_eis(world) (&((world)->store.entity_index)) +#define flecs_entities_init(world) flecs_entity_index_init(&world->allocator, ecs_eis(world)) +#define flecs_entities_fini(world) flecs_entity_index_fini(ecs_eis(world)) +#define flecs_entities_get(world, entity) flecs_entity_index_get(ecs_eis(world), entity) +#define flecs_entities_try(world, entity) flecs_entity_index_try_get(ecs_eis(world), entity) +#define flecs_entities_get_any(world, entity) flecs_entity_index_get_any(ecs_eis(world), entity) +#define flecs_entities_ensure(world, entity) flecs_entity_index_ensure(ecs_eis(world), entity) +#define flecs_entities_remove(world, entity) flecs_entity_index_remove(ecs_eis(world), entity) +#define flecs_entities_set_generation(world, entity) flecs_entity_index_set_generation(ecs_eis(world), entity) +#define flecs_entities_get_generation(world, entity) flecs_entity_index_get_generation(ecs_eis(world), entity) +#define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity) +#define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity) +#define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity) +#define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world)) +#define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count) +#define flecs_entities_max_id(world) (ecs_eis(world)->max_id) +#define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size) +#define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world)) +#define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world)) +#define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world)) +#define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world)) +#define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world)) +#define flecs_entities_copy(dst, src) flecs_entity_index_copy(dst, src) +#define flecs_entities_restore(dst, src) flecs_entity_index_restore(dst, src) + +#endif + +/** + * @file datastructures/stack_allocator.h + * @brief Stack allocator. + */ + +#ifndef FLECS_STACK_ALLOCATOR_H +#define FLECS_STACK_ALLOCATOR_H + +/** Stack allocator for quick allocation of small temporary values */ +#define ECS_STACK_PAGE_SIZE (4096) + +typedef struct ecs_stack_page_t { + void *data; + struct ecs_stack_page_t *next; + int16_t sp; + uint32_t id; +} ecs_stack_page_t; + +typedef struct ecs_stack_t { + ecs_stack_page_t first; + ecs_stack_page_t *tail_page; + ecs_stack_cursor_t *tail_cursor; +#ifdef FLECS_DEBUG + int32_t cursor_count; +#endif +} ecs_stack_t; + +FLECS_DBG_API +void flecs_stack_init( + ecs_stack_t *stack); + +FLECS_DBG_API +void flecs_stack_fini( + ecs_stack_t *stack); + +FLECS_DBG_API +void* flecs_stack_alloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align); + +#define flecs_stack_alloc_t(stack, T)\ + flecs_stack_alloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +#define flecs_stack_alloc_n(stack, T, count)\ + flecs_stack_alloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) + +FLECS_DBG_API +void* flecs_stack_calloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align); + +#define flecs_stack_calloc_t(stack, T)\ + flecs_stack_calloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +#define flecs_stack_calloc_n(stack, T, count)\ + flecs_stack_calloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) + +FLECS_DBG_API +void flecs_stack_free( + void *ptr, + ecs_size_t size); + +#define flecs_stack_free_t(ptr, T)\ + flecs_stack_free(ptr, ECS_SIZEOF(T)) + +#define flecs_stack_free_n(ptr, T, count)\ + flecs_stack_free(ptr, ECS_SIZEOF(T) * count) + +void flecs_stack_reset( + ecs_stack_t *stack); + +FLECS_DBG_API +ecs_stack_cursor_t* flecs_stack_get_cursor( + ecs_stack_t *stack); + +FLECS_DBG_API +void flecs_stack_restore_cursor( + ecs_stack_t *stack, + ecs_stack_cursor_t *cursor); + +#endif + +/** + * @file bitset.h + * @brief Bitset data structure. + */ + +#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. */ +FLECS_DBG_API +void flecs_bitset_init( + ecs_bitset_t *bs); + +/** Deinitialize bitset. */ +FLECS_DBG_API +void flecs_bitset_fini( + ecs_bitset_t *bs); + +/** Add n elements to bitset. */ +FLECS_DBG_API +void flecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count); + +/** Ensure element exists. */ +FLECS_DBG_API +void flecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count); + +/** Set element. */ +FLECS_DBG_API +void flecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value); + +/** Get element. */ +FLECS_DBG_API +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem); + +/** Return number of elements. */ +FLECS_DBG_API +int32_t flecs_bitset_count( + const ecs_bitset_t *bs); + +/** Remove from bitset. */ +FLECS_DBG_API +void flecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem); + +/** Swap values in bitset. */ +FLECS_DBG_API +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b); + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file switch_list.h + * @brief Interleaved linked list for storing mutually exclusive values. + */ + +#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; + +struct ecs_switch_t { + ecs_map_t hdrs; /* map */ + ecs_vec_t nodes; /* vec */ + ecs_vec_t values; /* vec */ +}; + +/** Init new switch. */ +FLECS_DBG_API +void flecs_switch_init( + ecs_switch_t* sw, + ecs_allocator_t *allocator, + int32_t elements); + +/** Fini switch. */ +FLECS_DBG_API +void flecs_switch_fini( + ecs_switch_t *sw); + +/** Remove all values. */ +FLECS_DBG_API +void flecs_switch_clear( + ecs_switch_t *sw); + +/** Add element to switch, initialize value to 0 */ +FLECS_DBG_API +void flecs_switch_add( + ecs_switch_t *sw); + +/** Set number of elements in switch list */ +FLECS_DBG_API +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count); + +/** Get number of elements */ +FLECS_DBG_API +int32_t flecs_switch_count( + ecs_switch_t *sw); + +/** Ensure that element exists. */ +FLECS_DBG_API +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count); + +/** Add n elements. */ +FLECS_DBG_API +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count); + +/** Set value of element. */ +FLECS_DBG_API +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value); + +/** Remove element. */ +FLECS_DBG_API +void flecs_switch_remove( + ecs_switch_t *sw, + int32_t element); + +/** Get value for element. */ +FLECS_DBG_API +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + int32_t element); + +/** Swap element. */ +FLECS_DBG_API +void flecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2); + +/** Get vector with all values. Use together with count(). */ +FLECS_DBG_API +ecs_vec_t* flecs_switch_values( + const ecs_switch_t *sw); + +/** Return number of different values. */ +FLECS_DBG_API +int32_t flecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value); + +/** Return first element for value. */ +FLECS_DBG_API +int32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value); + +/** Return next element for value. Use with first(). */ +FLECS_DBG_API +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t elem); + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file table.h + * @brief Table storage implementation. + */ + +#ifndef FLECS_TABLE_H +#define FLECS_TABLE_H + +/** + * @file table_graph.h + * @brief Table graph types and functions. + */ + +#ifndef FLECS_TABLE_GRAPH_H +#define FLECS_TABLE_GRAPH_H + +/** Cache of added/removed components for non-trivial edges between tables */ +#define ECS_TABLE_DIFF_INIT { .added = {0}} + +/** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to + * conserve memory on table edges (a type doesn't have the size field), whereas + * a vec for the builder is more convenient to use & has allocator support. */ +typedef struct ecs_table_diff_builder_t { + ecs_vec_t added; + ecs_vec_t removed; +} ecs_table_diff_builder_t; + +typedef struct ecs_table_diff_t { + ecs_type_t added; /* Components added between tables */ + ecs_type_t removed; /* Components removed between tables */ +} ecs_table_diff_t; + +/** Edge linked list (used to keep track of incoming edges) */ +typedef struct ecs_graph_edge_hdr_t { + struct ecs_graph_edge_hdr_t *prev; + struct ecs_graph_edge_hdr_t *next; +} ecs_graph_edge_hdr_t; + +/** Single edge. */ +typedef struct ecs_graph_edge_t { + ecs_graph_edge_hdr_t hdr; + ecs_table_t *from; /* Edge source table */ + ecs_table_t *to; /* Edge destination table */ + ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ + ecs_id_t id; /* Id associated with edge */ +} ecs_graph_edge_t; + +/* Edges to other tables. */ +typedef struct ecs_graph_edges_t { + ecs_graph_edge_t *lo; /* Small array optimized for low edges */ + ecs_map_t *hi; /* Map for hi edges (map) */ +} ecs_graph_edges_t; + +/* Table graph node */ +typedef struct ecs_graph_node_t { + /* Outgoing edges */ + ecs_graph_edges_t add; + ecs_graph_edges_t remove; + + /* Incoming edges (next = add edges, prev = remove edges) */ + ecs_graph_edge_hdr_t refs; +} ecs_graph_node_t; + +/* Find table by adding id to current table */ +ecs_table_t *flecs_table_traverse_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff); + +/* Find table by removing id from current table */ +ecs_table_t *flecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff); + +/* Cleanup incoming and outgoing edges for table */ +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table); + +/* Table diff builder, used to build id lists that indicate the difference in + * ids between two tables. */ +void flecs_table_diff_builder_init( + ecs_world_t *world, + ecs_table_diff_builder_t *builder); + +void flecs_table_diff_builder_fini( + ecs_world_t *world, + ecs_table_diff_builder_t *builder); + +void flecs_table_diff_builder_clear( + ecs_table_diff_builder_t *builder); + +void flecs_table_diff_build_append_table( + ecs_world_t *world, + ecs_table_diff_builder_t *dst, + ecs_table_diff_t *src); + +void flecs_table_diff_build( + ecs_world_t *world, + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff, + int32_t added_offset, + int32_t removed_offset); + +void flecs_table_diff_build_noalloc( + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff); + +#endif + + +/* Table event type for notifying tables of world events */ +typedef enum ecs_table_eventkind_t { + EcsTableTriggersForId, + EcsTableNoTriggersForId, +} ecs_table_eventkind_t; + +typedef struct ecs_table_event_t { + ecs_table_eventkind_t kind; + + /* Query event */ + ecs_query_t *query; + + /* Component info event */ + ecs_entity_t component; + + /* Event match */ + ecs_entity_t event; + + /* If the number 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; + +/** Infrequently accessed data not stored inline in ecs_table_t */ +typedef struct ecs_table__t { + uint64_t hash; /* Type hash */ + int32_t lock; /* Prevents modifications */ + int32_t traversable_count; /* Traversable relationship targets in table */ + uint16_t generation; /* Used for table cleanup */ + int16_t record_count; /* Table record count including wildcards */ + + struct ecs_table_record_t *records; /* Array with table records */ + ecs_hashmap_t *name_index; /* Cached pointer to name index */ + + ecs_switch_t *sw_columns; /* Switch columns */ + ecs_bitset_t *bs_columns; /* Bitset columns */ + int16_t sw_count; + int16_t sw_offset; + int16_t bs_count; + int16_t bs_offset; + int16_t ft_offset; +} ecs_table__t; + +/** Table column */ +typedef struct ecs_column_t { + ecs_vec_t data; /* Vector with component data */ + ecs_id_t id; /* Component id */ + ecs_type_info_t *ti; /* Component type info */ + ecs_size_t size; /* Component size */ +} ecs_column_t; + +/** Table data */ +struct ecs_data_t { + ecs_vec_t entities; /* Entity ids */ + ecs_column_t *columns; /* Component data */ +}; + +/** 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 { + uint64_t id; /* Table id in sparse set */ + ecs_flags32_t flags; /* Flags for testing table properties */ + int16_t column_count; /* Number of components (excluding tags) */ + ecs_type_t type; /* Vector with component ids */ + + ecs_data_t data; /* Component storage */ + ecs_graph_node_t node; /* Graph node */ + + int32_t *dirty_state; /* Keep track of changes in columns */ + int32_t *column_map; /* Map type index <-> column + * - 0..count(T): type index -> column + * - count(T)..count(C): column -> type index + */ + + ecs_table__t *_; /* Infrequently accessed table metadata */ +}; + +/* Init table */ +void flecs_table_init( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *from); + +/** Copy type. */ +ecs_type_t flecs_type_copy( + ecs_world_t *world, + const ecs_type_t *src); + +/** Free type. */ +void flecs_type_free( + ecs_world_t *world, + ecs_type_t *type); + +/** Find or create table for a set of components */ +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + ecs_type_t *type); + +/* Initialize columns for data */ +void flecs_table_init_data( + ecs_world_t *world, + ecs_table_t *table); + +/* Clear all entities from a table. */ +void flecs_table_clear_entities( + ecs_world_t *world, + ecs_table_t *table); + +/* Reset a table to its initial state */ +void flecs_table_reset( + ecs_world_t *world, + ecs_table_t *table); + +/* Clear all entities from the table. Do not invoke OnRemove systems */ +void flecs_table_clear_entities_silent( + ecs_world_t *world, + ecs_table_t *table); + +/* Clear table data. Don't call OnRemove handlers. */ +void flecs_table_clear_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* Return number of entities in data */ +int32_t flecs_table_data_count( + const ecs_data_t *data); + +/* Add a new entry to the table for the specified entity */ +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + bool construct, + bool on_add); + +/* Delete an entity from the table. */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + int32_t index, + bool destruct); + +/* Make sure table records are in correct table cache list */ +bool flecs_table_records_update_empty( + ecs_table_t *table); + +/* Move a row from one table to another */ +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + int32_t new_index, + ecs_table_t *old_table, + int32_t old_index, + bool construct); + +/* Grow table with specified number of records. Populate table with entities, + * starting from specified entity id. */ +int32_t flecs_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 flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t count); + +/* Shrink table to contents */ +bool flecs_table_shrink( + ecs_world_t *world, + ecs_table_t *table); + +/* Get dirty state for table columns */ +int32_t* flecs_table_get_dirty_state( + ecs_world_t *world, + ecs_table_t *table); + +/* Initialize root table */ +void flecs_init_root_table( + ecs_world_t *world); + +/* Unset components in table */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table); + +/* Free table */ +void flecs_table_free( + ecs_world_t *world, + ecs_table_t *table); + +/* Free table */ +void flecs_table_free_type( + ecs_world_t *world, + ecs_table_t *table); + +/* Replace data */ +void flecs_table_replace_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* Merge data of one table into another table */ +void flecs_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 flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + int32_t row_1, + int32_t row_2); + +void flecs_table_mark_dirty( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component); + +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t *event); + +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table); + +int32_t flecs_table_column_to_union_index( + const ecs_table_t *table, + int32_t column); + +/* Increase observer count of table */ +void flecs_table_traversable_add( + ecs_table_t *table, + int32_t value); + +ecs_vec_t* flecs_table_entities( + ecs_table_t *table); + +ecs_entity_t* flecs_table_entities_array( + ecs_table_t *table); + +#endif + + +/* Used in id records to keep track of entities used with id flags */ +extern const ecs_entity_t EcsFlag; + +#define ECS_MAX_JOBS_PER_WORKER (16) +#define ECS_MAX_DEFER_STACK (8) + +/* Magic number for a flecs object */ +#define ECS_OBJECT_MAGIC (0x6563736f) + +/* Tags associated with poly for (Poly, tag) components */ +#define ecs_world_t_tag invalid +#define ecs_stage_t_tag invalid +#define ecs_query_t_tag EcsQuery +#define ecs_rule_t_tag EcsQuery +#define ecs_table_t_tag invalid +#define ecs_filter_t_tag EcsQuery +#define ecs_observer_t_tag EcsObserver + +/* Mixin kinds */ +typedef enum ecs_mixin_kind_t { + EcsMixinWorld, + EcsMixinEntity, + EcsMixinObservable, + EcsMixinIterable, + EcsMixinDtor, + EcsMixinMax +} ecs_mixin_kind_t; + +/* The mixin array contains pointers to mixin members for different kinds of + * flecs objects. This allows the API to retrieve data from an object regardless + * of its type. Each mixin array is only stored once per type */ +struct ecs_mixins_t { + const char *type_name; /* Include name of mixin type so debug code doesn't + * need to know about every object */ + ecs_size_t elems[EcsMixinMax]; +}; + +/* Mixin tables */ +extern ecs_mixins_t ecs_world_t_mixins; +extern ecs_mixins_t ecs_stage_t_mixins; +extern ecs_mixins_t ecs_filter_t_mixins; +extern ecs_mixins_t ecs_query_t_mixins; +extern ecs_mixins_t ecs_trigger_t_mixins; +extern ecs_mixins_t ecs_observer_t_mixins; + +/* Types that have no mixins */ +#define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) + +/* Scope for flecs internals, like observers used for builtin features */ +extern const ecs_entity_t EcsFlecsInternals; + +/** Type used for internal string hashmap */ +typedef struct ecs_hashed_string_t { + char *value; + ecs_size_t length; + uint64_t hash; +} ecs_hashed_string_t; + +/** Must appear as first member in payload of table cache */ +typedef struct ecs_table_cache_hdr_t { + struct ecs_table_cache_t *cache; + ecs_table_t *table; + struct ecs_table_cache_hdr_t *prev, *next; + bool empty; +} ecs_table_cache_hdr_t; + +/** Linked list of tables in table cache */ +typedef struct ecs_table_cache_list_t { + ecs_table_cache_hdr_t *first; + ecs_table_cache_hdr_t *last; + int32_t count; +} ecs_table_cache_list_t; + +/** Table cache */ +typedef struct ecs_table_cache_t { + ecs_map_t index; /* */ + ecs_table_cache_list_t tables; + ecs_table_cache_list_t empty_tables; +} ecs_table_cache_t; + +/* Sparse query term */ +typedef struct flecs_switch_term_t { + ecs_switch_t *sw_column; + ecs_entity_t sw_case; + int32_t signature_column_index; +} flecs_switch_term_t; + +/* Bitset query term */ +typedef struct flecs_bitset_term_t { + ecs_bitset_t *bs_column; + int32_t column_index; +} flecs_bitset_term_t; + +typedef struct flecs_flat_monitor_t { + int32_t table_state; + int32_t monitor; +} flecs_flat_monitor_t; + +/* Flat table term */ +typedef struct flecs_flat_table_term_t { + int32_t field_index; /* Iterator field index */ + ecs_term_t *term; + ecs_vec_t monitor; +} flecs_flat_table_term_t; + +/* Entity filter. This filters the entities of a matched table, for example when + * it has disabled components or union relationships (switch). */ +typedef struct ecs_entity_filter_t { + ecs_vec_t sw_terms; /* Terms with switch (union) entity filter */ + ecs_vec_t bs_terms; /* Terms with bitset (toggle) entity filter */ + ecs_vec_t ft_terms; /* Terms with components from flattened tree */ + int32_t flat_tree_column; +} ecs_entity_filter_t; + +typedef struct ecs_entity_filter_iter_t { + ecs_entity_filter_t *entity_filter; + ecs_iter_t *it; + int32_t *columns; + ecs_table_t *prev; + ecs_table_range_t range; + int32_t bs_offset; + int32_t sw_offset; + int32_t sw_smallest; + int32_t flat_tree_offset; + int32_t target_count; +} ecs_entity_filter_iter_t; + +/** Table match data. + * Each table matched by the query is represented by a ecs_query_table_match_t + * instance, which are linked together in a list. A table may match a query + * multiple times (due to wildcard queries) with different columns being matched + * by the query. */ +struct ecs_query_table_match_t { + ecs_query_table_match_t *next, *prev; + ecs_table_t *table; /* The current table. */ + int32_t offset; /* Starting point in table */ + int32_t count; /* Number of entities to iterate in table */ + int32_t *columns; /* Mapping from query fields to table columns */ + int32_t *storage_columns; /* Mapping from query fields to storage columns */ + ecs_id_t *ids; /* Resolved (component) ids for current table */ + ecs_entity_t *sources; /* Subjects (sources) of ids */ + ecs_vec_t refs; /* Cached components for non-this terms */ + uint64_t group_id; /* Value used to organize tables in groups */ + int32_t *monitor; /* Used to monitor table for changes */ + ecs_entity_filter_t *entity_filter; /* Entity specific filters */ + + /* Next match in cache for same table (includes empty tables) */ + ecs_query_table_match_t *next_match; +}; + +/** Table record type for query table cache. A query only has one per table. */ +typedef struct ecs_query_table_t { + ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ + ecs_query_table_match_t *first; /* List with matches for table */ + ecs_query_table_match_t *last; /* Last discovered match for table */ + uint64_t table_id; + int32_t rematch_count; /* Track whether table was rematched */ +} ecs_query_table_t; + +/** Points to the beginning & ending of a query group */ +typedef struct ecs_query_table_list_t { + ecs_query_table_match_t *first; + ecs_query_table_match_t *last; + ecs_query_group_info_t info; +} ecs_query_table_list_t; + +/* Query event type for notifying queries of world events */ +typedef enum ecs_query_eventkind_t { + EcsQueryTableMatch, + 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 level block allocators have sizes that depend on query field count */ +typedef struct ecs_query_allocators_t { + ecs_block_allocator_t columns; + ecs_block_allocator_t ids; + ecs_block_allocator_t sources; + ecs_block_allocator_t monitors; +} ecs_query_allocators_t; + +/** Query that is automatically matched against tables */ +struct ecs_query_t { + ecs_header_t hdr; + + /* Query filter */ + ecs_filter_t filter; + + /* Tables matched with query */ + ecs_table_cache_t cache; + + /* Linked list with all matched non-empty tables, in iteration order */ + ecs_query_table_list_t list; + + /* Contains head/tail to nodes of query groups (if group_by is used) */ + ecs_map_t groups; + + /* Table sorting */ + ecs_entity_t order_by_component; + ecs_order_by_action_t order_by; + ecs_sort_table_action_t sort_table; + ecs_vec_t table_slices; + int32_t order_by_term; + + /* Table grouping */ + ecs_entity_t group_by_id; + ecs_group_by_action_t group_by; + ecs_group_create_action_t on_group_create; + ecs_group_delete_action_t on_group_delete; + void *group_by_ctx; + ecs_ctx_free_t group_by_ctx_free; + + /* Subqueries */ + ecs_query_t *parent; + ecs_vec_t subqueries; + + /* Flags for query properties */ + ecs_flags32_t flags; + + /* Monitor generation */ + int32_t monitor_generation; + + int32_t cascade_by; /* Identify cascade term */ + int32_t match_count; /* How often have tables been (un)matched */ + int32_t prev_match_count; /* Track if sorting is needed */ + int32_t rematch_count; /* Track which tables were added during rematch */ + + /* User context */ + void *ctx; /* User context to pass to callback */ + void *binding_ctx; /* Context to be used for language bindings */ + + ecs_ctx_free_t ctx_free; /** Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /** Callback to free binding_ctx */ + + /* Mixins */ + ecs_iterable_t iterable; + ecs_poly_dtor_t dtor; + + /* Query-level allocators */ + ecs_query_allocators_t allocators; +}; + +/** All observers for a specific (component) id */ +typedef struct ecs_event_id_record_t { + /* Triggers for Self */ + ecs_map_t self; /* map */ + ecs_map_t self_up; /* map */ + ecs_map_t up; /* map */ + + ecs_map_t observers; /* map */ + + /* Triggers for SuperSet, SubSet */ + ecs_map_t set_observers; /* map */ + + /* Triggers for Self with non-This subject */ + ecs_map_t entity_observers; /* map */ + + /* Number of active observers for (component) id */ + int32_t observer_count; +} ecs_event_id_record_t; + +/* World level allocators are for operations that are not multithreaded */ +typedef struct ecs_world_allocators_t { + ecs_map_params_t ptr; + ecs_map_params_t query_table_list; + ecs_block_allocator_t query_table; + ecs_block_allocator_t query_table_match; + ecs_block_allocator_t graph_edge_lo; + ecs_block_allocator_t graph_edge; + ecs_block_allocator_t id_record; + ecs_block_allocator_t id_record_chunk; + ecs_block_allocator_t table_diff; + ecs_block_allocator_t sparse_chunk; + ecs_block_allocator_t hashmap; + + /* Temporary vectors used for creating table diff id sequences */ + ecs_table_diff_builder_t diff_builder; +} ecs_world_allocators_t; + +/* Stage level allocators are for operations that can be multithreaded */ +typedef struct ecs_stage_allocators_t { + ecs_stack_t iter_stack; + ecs_stack_t deser_stack; + ecs_block_allocator_t cmd_entry_chunk; +} ecs_stage_allocators_t; + +/** Types for deferred operations */ +typedef enum ecs_cmd_kind_t { + EcsCmdClone, + EcsCmdBulkNew, + EcsCmdAdd, + EcsCmdRemove, + EcsCmdSet, + EcsCmdEmplace, + EcsCmdMut, + EcsCmdModified, + EcsCmdModifiedNoHook, + EcsCmdAddModified, + EcsCmdPath, + EcsCmdDelete, + EcsCmdClear, + EcsCmdOnDeleteAction, + EcsCmdEnable, + EcsCmdDisable, + EcsCmdEvent, + EcsCmdSkip +} ecs_cmd_kind_t; + +/* Entity specific metadata for command in queue */ +typedef struct ecs_cmd_entry_t { + int32_t first; + int32_t last; /* If -1, a delete command was inserted */ +} ecs_cmd_entry_t; + +typedef struct ecs_cmd_1_t { + void *value; /* Component value (used by set / get_mut) */ + ecs_size_t size; /* Size of value */ + bool clone_value; /* Clone entity with value (used for clone) */ +} ecs_cmd_1_t; + +typedef struct ecs_cmd_n_t { + ecs_entity_t *entities; + int32_t count; +} ecs_cmd_n_t; + +typedef struct ecs_cmd_t { + ecs_cmd_kind_t kind; /* Command kind */ + int32_t next_for_entity; /* Next operation for entity */ + ecs_id_t id; /* (Component) id */ + ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ + ecs_cmd_entry_t *entry; + ecs_entity_t entity; /* Entity id */ + + union { + ecs_cmd_1_t _1; /* Data for single entity operation */ + ecs_cmd_n_t _n; /* Data for multi entity operation */ + } is; +} ecs_cmd_t; + +/* Data structures that store the command queue */ +typedef struct ecs_commands_t { + ecs_vec_t queue; + ecs_stack_t stack; /* Temp memory used by deferred commands */ + ecs_sparse_t entries; /* - command batching */ +} ecs_commands_t; + +/** A stage is a context that allows for safely using the API from multiple + * threads. Stage pointers can be passed to the world argument of API + * operations, which causes the operation to be ran on the stage instead of the + * world. */ +struct ecs_stage_t { + ecs_header_t hdr; + + /* Unique id that identifies the stage */ + int32_t id; + + /* Zero if not deferred, positive if deferred, negative if suspended */ + int32_t defer; + + /* Command queue stack, for nested execution */ + ecs_commands_t *cmd; + ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; + int32_t cmd_sp; + + /* Thread context */ + ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ + ecs_world_t *world; /* Reference to world */ + ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ + + /* One-shot actions to be executed after the merge */ + ecs_vec_t post_frame_actions; + + /* Namespacing */ + ecs_entity_t scope; /* Entity of current scope */ + ecs_entity_t with; /* Id to add by default to new entities */ + ecs_entity_t base; /* Currently instantiated top-level base */ + const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ + + /* Properties */ + bool auto_merge; /* Should this stage automatically merge? */ + bool async; /* Is stage asynchronous? (write only) */ + + /* Thread specific allocators */ + ecs_stage_allocators_t allocators; + ecs_allocator_t allocator; + + /* Caches for rule creation */ + ecs_vec_t variables; + ecs_vec_t operations; +}; + +/* Component monitor */ +typedef struct ecs_monitor_t { + ecs_vec_t queries; /* vector */ + bool is_dirty; /* Should queries be rematched? */ +} ecs_monitor_t; + +/* Component monitors */ +typedef struct ecs_monitor_set_t { + ecs_map_t monitors; /* map */ + bool is_dirty; /* Should monitors be evaluated? */ +} ecs_monitor_set_t; + +/* Data stored for id marked for deletion */ +typedef struct ecs_marked_id_t { + ecs_id_record_t *idr; + ecs_id_t id; + ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ + bool delete_id; +} ecs_marked_id_t; + +typedef struct ecs_store_t { + /* Entity lookup */ + ecs_entity_index_t entity_index; + + /* Table lookup by id */ + ecs_sparse_t tables; /* sparse */ + + /* Table lookup by hash */ + ecs_hashmap_t table_map; /* hashmap */ + + /* Root table */ + ecs_table_t root; + + /* Records cache */ + ecs_vec_t records; + + /* Stack of ids being deleted. */ + ecs_vec_t marked_ids; /* vector */ + + /* Entity ids associated with depth (for flat hierarchies) */ + ecs_vec_t depth_ids; + ecs_map_t entity_to_depth; /* What it says */ +} ecs_store_t; + +/* fini actions */ +typedef struct ecs_action_elem_t { + ecs_fini_action_t action; + void *ctx; +} ecs_action_elem_t; + +typedef struct ecs_pipeline_state_t ecs_pipeline_state_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 { + ecs_header_t hdr; + + /* -- Type metadata -- */ + ecs_id_record_t *id_index_lo; + ecs_map_t id_index_hi; /* map */ + ecs_sparse_t type_info; /* sparse */ + + /* -- Cached handle to id records -- */ + ecs_id_record_t *idr_wildcard; + ecs_id_record_t *idr_wildcard_wildcard; + ecs_id_record_t *idr_any; + ecs_id_record_t *idr_isa_wildcard; + ecs_id_record_t *idr_childof_0; + ecs_id_record_t *idr_childof_wildcard; + ecs_id_record_t *idr_identifier_name; + + /* -- Mixins -- */ + ecs_world_t *self; + ecs_observable_t observable; + ecs_iterable_t iterable; + + /* Unique id per generated event used to prevent duplicate notifications */ + int32_t event_id; + + /* Is entity range checking enabled? */ + bool range_check_enabled; + + /* -- Data storage -- */ + ecs_store_t store; + + /* -- Pending table event buffers -- */ + ecs_sparse_t *pending_buffer; /* sparse */ + ecs_sparse_t *pending_tables; /* sparse */ + + /* Used to track when cache needs to be updated */ + ecs_monitor_set_t monitors; /* map */ + + /* -- Systems -- */ + ecs_entity_t pipeline; /* Current pipeline */ + + /* -- Identifiers -- */ + ecs_hashmap_t aliases; + ecs_hashmap_t symbols; + + /* -- Staging -- */ + ecs_stage_t *stages; /* Stages */ + int32_t stage_count; /* Number of stages */ + + /* -- Multithreading -- */ + 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 */ + ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ + bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ + + /* -- Time management -- */ + ecs_time_t world_start_time; /* Timestamp of simulation start */ + ecs_time_t frame_start_time; /* Timestamp of frame start */ + ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ + + /* -- Metrics -- */ + ecs_world_info_t info; + + /* -- World flags -- */ + ecs_flags32_t flags; + + /* Count that increases when component monitors change */ + int32_t monitor_generation; + + /* -- Allocators -- */ + ecs_world_allocators_t allocators; /* Static allocation sizes */ + ecs_allocator_t allocator; /* Dynamic allocation sizes */ + + void *ctx; /* Application context */ + void *binding_ctx; /* Binding-specific context */ + + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ + + ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ +}; + +#endif + +/** + * @file table_cache.h + * @brief Data structure for fast table iteration/lookups. + */ + +#ifndef FLECS_TABLE_CACHE_H_ +#define FLECS_TABLE_CACHE_H_ + +void ecs_table_cache_init( + ecs_world_t *world, + ecs_table_cache_t *cache); + +void ecs_table_cache_fini( + ecs_table_cache_t *cache); + +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result); + +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem); + +void* ecs_table_cache_remove( + ecs_table_cache_t *cache, + uint64_t table_id, + ecs_table_cache_hdr_t *elem); + +void* ecs_table_cache_get( + const ecs_table_cache_t *cache, + const ecs_table_t *table); + +bool ecs_table_cache_set_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + bool empty); + +bool ecs_table_cache_is_empty( + const ecs_table_cache_t *cache); + +#define flecs_table_cache_count(cache) (cache)->tables.count +#define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count + +bool flecs_table_cache_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); + +bool flecs_table_cache_empty_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); + +bool flecs_table_cache_all_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); + +ecs_table_cache_hdr_t* flecs_table_cache_next_( + ecs_table_cache_iter_t *it); + +#define flecs_table_cache_next(it, T)\ + (ECS_CAST(T*, flecs_table_cache_next_(it))) + +#endif + +/** + * @file id_index.h + * @brief Index for looking up tables by (component) id. + */ + +#ifndef FLECS_ID_INDEX_H +#define FLECS_ID_INDEX_H + +/* Payload for id cache */ +struct ecs_table_record_t { + ecs_table_cache_hdr_t hdr; /* Table cache header */ + int16_t index; /* First type index where id occurs in table */ + int16_t count; /* Number of times id occurs in table */ + int16_t column; /* First column index where id occurs */ +}; + +/* Linked list of id records */ +typedef struct ecs_id_record_elem_t { + struct ecs_id_record_t *prev, *next; +} ecs_id_record_elem_t; + +typedef struct ecs_reachable_elem_t { + const ecs_table_record_t *tr; + ecs_record_t *record; + ecs_entity_t src; + ecs_id_t id; +#ifndef NDEBUG + ecs_table_t *table; +#endif +} ecs_reachable_elem_t; + +typedef struct ecs_reachable_cache_t { + int32_t generation; + int32_t current; + ecs_vec_t ids; /* vec */ +} ecs_reachable_cache_t; + +/* Payload for id index which contains all data structures for an id. */ +struct ecs_id_record_t { + /* Cache with all tables that contain the id. Must be first member. */ + ecs_table_cache_t cache; /* table_cache */ + + /* Id of record */ + ecs_id_t id; + + /* Flags for id */ + ecs_flags32_t flags; + + /* Cached pointer to type info for id, if id contains data. */ + const ecs_type_info_t *type_info; + + /* Name lookup index (currently only used for ChildOf pairs) */ + ecs_hashmap_t *name_index; + + /* Lists for all id records that match a pair wildcard. The wildcard id + * record is at the head of the list. */ + ecs_id_record_elem_t first; /* (R, *) */ + ecs_id_record_elem_t second; /* (*, O) */ + ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ + + /* Parent id record. For pair records the parent is the (R, *) record. */ + ecs_id_record_t *parent; + + /* Refcount */ + int32_t refcount; + + /* Keep alive count. This count must be 0 when the id record is deleted. If + * it is not 0, an application attempted to delete an id that was still + * queried for. */ + int32_t keep_alive; + + /* Cache invalidation counter */ + ecs_reachable_cache_t reachable; +}; + +/* Get id record for id */ +ecs_id_record_t* flecs_id_record_get( + const ecs_world_t *world, + ecs_id_t id); + +/* Get id record for id for searching. + * Same as flecs_id_record_get, but replaces (R, *) with (Union, R) if R is a + * union relationship. */ +ecs_id_record_t* flecs_query_id_record_get( + const ecs_world_t *world, + ecs_id_t id); + +/* Ensure id record for id */ +ecs_id_record_t* flecs_id_record_ensure( + ecs_world_t *world, + ecs_id_t id); + +/* Increase refcount of id record */ +void flecs_id_record_claim( + ecs_world_t *world, + ecs_id_record_t *idr); + +/* Decrease refcount of id record, delete if 0 */ +int32_t flecs_id_record_release( + ecs_world_t *world, + ecs_id_record_t *idr); + +/* Release all empty tables in id record */ +void flecs_id_record_release_tables( + ecs_world_t *world, + ecs_id_record_t *idr); + +/* Set (component) type info for id record */ +bool flecs_id_record_set_type_info( + ecs_world_t *world, + ecs_id_record_t *idr, + const ecs_type_info_t *ti); + +/* Ensure id record has name index */ +ecs_hashmap_t* flecs_id_name_index_ensure( + ecs_world_t *world, + ecs_id_t id); + +ecs_hashmap_t* flecs_id_record_name_index_ensure( + ecs_world_t *world, + ecs_id_record_t *idr); + +/* Get name index for id record */ +ecs_hashmap_t* flecs_id_name_index_get( + const ecs_world_t *world, + ecs_id_t id); + +/* Find table record for id */ +ecs_table_record_t* flecs_table_record_get( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +/* Find table record for id record */ +ecs_table_record_t* flecs_id_record_get_table( + const ecs_id_record_t *idr, + const ecs_table_t *table); + +/* Bootstrap cached id records */ +void flecs_init_id_records( + ecs_world_t *world); + +/* Cleanup all id records in world */ +void flecs_fini_id_records( + ecs_world_t *world); + +#endif + +/** + * @file observable.h + * @brief Functions for sending events. + */ + +#ifndef FLECS_OBSERVABLE_H +#define FLECS_OBSERVABLE_H + +ecs_event_record_t* flecs_event_record_get( + const ecs_observable_t *o, + ecs_entity_t event); + +ecs_event_record_t* flecs_event_record_ensure( + ecs_observable_t *o, + ecs_entity_t event); + +ecs_event_id_record_t* flecs_event_id_record_get( + const ecs_event_record_t *er, + ecs_id_t id); + +ecs_event_id_record_t* flecs_event_id_record_ensure( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_id_t id); + +void flecs_event_id_record_remove( + ecs_event_record_t *er, + ecs_id_t id); + +void flecs_observable_init( + ecs_observable_t *observable); + +void flecs_observable_fini( + ecs_observable_t *observable); + +bool flecs_observers_exist( + ecs_observable_t *observable, + ecs_id_t id, + ecs_entity_t event); + +void flecs_observer_fini( + ecs_observer_t *observer); + +void flecs_emit( + ecs_world_t *world, + ecs_world_t *stage, + ecs_event_desc_t *desc); + +bool flecs_default_observer_next_callback( + ecs_iter_t *it); + +void flecs_observers_invoke( + ecs_world_t *world, + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav); + +void flecs_emit_propagate_invalidate( + ecs_world_t *world, + ecs_table_t *table, + int32_t offset, + int32_t count); + +#endif + +/** + * @file iter.h + * @brief Iterator utilities. + */ + +#ifndef FLECS_ITER_H +#define FLECS_ITER_H + +void flecs_iter_init( + const ecs_world_t *world, + ecs_iter_t *it, + ecs_flags8_t fields); + +void flecs_iter_validate( + ecs_iter_t *it); + +void flecs_iter_populate_data( + ecs_world_t *world, + ecs_iter_t *it, + ecs_table_t *table, + int32_t offset, + int32_t count, + void **ptrs); + +bool flecs_iter_next_row( + ecs_iter_t *it); + +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result); + +void* flecs_iter_calloc( + ecs_iter_t *it, + ecs_size_t size, + ecs_size_t align); + +#define flecs_iter_calloc_t(it, T)\ + flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +#define flecs_iter_calloc_n(it, T, count)\ + flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) + +void flecs_iter_free( + void *ptr, + ecs_size_t size); + +#define flecs_iter_free_t(ptr, T)\ + flecs_iter_free(ptr, ECS_SIZEOF(T)) + +#define flecs_iter_free_n(ptr, T, count)\ + flecs_iter_free(ptr, ECS_SIZEOF(T) * count) + +#endif + +/** + * @file poly.h + * @brief Functions for managing poly objects. + */ + +#ifndef FLECS_POLY_H +#define FLECS_POLY_H + +#include + +/* Initialize poly */ +void* ecs_poly_init_( + ecs_poly_t *object, + int32_t kind, + ecs_size_t size, + ecs_mixins_t *mixins); + +#define ecs_poly_init(object, type)\ + ecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) + +/* Deinitialize object for specified type */ +void ecs_poly_fini_( + ecs_poly_t *object, + int32_t kind); + +#define ecs_poly_fini(object, type)\ + ecs_poly_fini_(object, type##_magic) + +/* Utility functions for creating an object on the heap */ +#define ecs_poly_new(type)\ + (type*)ecs_poly_init(ecs_os_calloc_t(type), type) + +#define ecs_poly_free(obj, type)\ + ecs_poly_fini(obj, type);\ + ecs_os_free(obj) + +/* Get or create poly component for an entity */ +EcsPoly* ecs_poly_bind_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); + +#define ecs_poly_bind(world, entity, T) \ + ecs_poly_bind_(world, entity, T##_tag) + +void ecs_poly_modified_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); + +#define ecs_poly_modified(world, entity, T) \ + ecs_poly_modified_(world, entity, T##_tag) + +/* Get poly component for an entity */ +const EcsPoly* ecs_poly_bind_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); + +#define ecs_poly_bind_get(world, entity, T) \ + ecs_poly_bind_get_(world, entity, T##_tag) + +ecs_poly_t* ecs_poly_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); + +#define ecs_poly_get(world, entity, T) \ + ((T*)ecs_poly_get_(world, entity, T##_tag)) + +/* Utilities for testing/asserting an object type */ +#ifndef FLECS_NDEBUG +#define ecs_poly_assert(object, ty)\ + do {\ + ecs_assert(object != NULL, ECS_INVALID_PARAMETER, NULL);\ + const ecs_header_t *hdr = (const ecs_header_t *)object;\ + const char *type_name = hdr->mixins->type_name;\ + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, type_name);\ + ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, type_name);\ + } while (0) +#else +#define ecs_poly_assert(object, ty) +#endif + +/* Utility functions for getting a mixin from an object */ +ecs_iterable_t* ecs_get_iterable( + const ecs_poly_t *poly); + +ecs_observable_t* ecs_get_observable( + const ecs_poly_t *object); + +ecs_poly_dtor_t* ecs_get_dtor( + const ecs_poly_t *poly); + +#endif + +/** + * @file stage.h + * @brief Stage functions. + */ + +#ifndef FLECS_STAGE_H +#define FLECS_STAGE_H + +/* Initialize stage data structures */ +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Deinitialize stage */ +void flecs_stage_fini( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Post-frame merge actions */ +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_cmd( + ecs_stage_t *stage); + +bool flecs_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_modified( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component); + +bool flecs_defer_clone( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value); + +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_id_t id, + const ecs_entity_t **ids_out); + +bool flecs_defer_path( + ecs_stage_t *stage, + ecs_entity_t parent, + ecs_entity_t entity, + const char *name); + +bool flecs_defer_delete( + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_clear( + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_on_delete_action( + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action); + +bool flecs_defer_enable( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable); + +bool flecs_defer_add( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id); + +bool flecs_defer_remove( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id); + +void* flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_cmd_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + void *value); + +bool flecs_defer_end( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage); + +void flecs_enqueue( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_event_desc_t *desc); + +void flecs_commands_push( + ecs_stage_t *stage); + +void flecs_commands_pop( + ecs_stage_t *stage); + +#endif + +/** + * @file world.h + * @brief World-level API. + */ + +#ifndef FLECS_WORLD_H +#define FLECS_WORLD_H + +/* Get current stage */ +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr); + +/* Get current thread-specific stage from readonly world */ +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world); + +/* Get component callbacks */ +const ecs_type_info_t *flecs_type_info_get( + const ecs_world_t *world, + ecs_entity_t component); + +/* Get or create component callbacks */ +ecs_type_info_t* flecs_type_info_ensure( + ecs_world_t *world, + ecs_entity_t component); + +bool flecs_type_info_init_id( + ecs_world_t *world, + ecs_entity_t component, + ecs_size_t size, + ecs_size_t alignment, + const ecs_type_hooks_t *li); + +#define flecs_type_info_init(world, T, ...)\ + flecs_type_info_init_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\ + &(ecs_type_hooks_t)__VA_ARGS__) + +void flecs_type_info_fini( + ecs_type_info_t *ti); + +void flecs_type_info_free( + ecs_world_t *world, + ecs_entity_t component); + +void flecs_eval_component_monitors( + ecs_world_t *world); + +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t id); + +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query); + +void flecs_monitor_unregister( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query); + +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event); + +void flecs_register_table( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_unregister_table( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_table_set_empty( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_process_pending_tables( + const ecs_world_t *world); + +/* Suspend/resume readonly state. To fully support implicit registration of + * components, it should be possible to register components while the world is + * in readonly mode. It is not uncommon that a component is used first from + * within a system, which are often ran while in readonly mode. + * + * Suspending readonly mode is only allowed when the world is not multithreaded. + * When a world is multithreaded, it is not safe to (even temporarily) leave + * readonly mode, so a multithreaded application should always explicitly + * register components in advance. + * + * These operations also suspend deferred mode. + */ +typedef struct ecs_suspend_readonly_state_t { + bool is_readonly; + bool is_deferred; + int32_t defer_count; + ecs_entity_t scope; + ecs_entity_t with; + ecs_vec_t commands; + ecs_stack_t defer_stack; + ecs_stage_t *stage; +} ecs_suspend_readonly_state_t; + +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +/* Convenience macro's for world allocator */ +#define flecs_walloc(world, size)\ + flecs_alloc(&world->allocator, size) +#define flecs_walloc_n(world, T, count)\ + flecs_alloc_n(&world->allocator, T, count) +#define flecs_wcalloc(world, size)\ + flecs_calloc(&world->allocator, size) +#define flecs_wcalloc_n(world, T, count)\ + flecs_calloc_n(&world->allocator, T, count) +#define flecs_wfree(world, size, ptr)\ + flecs_free(&world->allocator, size, ptr) +#define flecs_wfree_n(world, T, count, ptr)\ + flecs_free_n(&world->allocator, T, count, ptr) +#define flecs_wrealloc(world, size_dst, size_src, ptr)\ + flecs_realloc(&world->allocator, size_dst, size_src, ptr) +#define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\ + flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr) +#define flecs_wdup(world, size, ptr)\ + flecs_dup(&world->allocator, size, ptr) +#define flecs_wdup_n(world, T, count, ptr)\ + flecs_dup_n(&world->allocator, T, count, ptr) + +#endif + +/** + * @file datastructures/name_index.h + * @brief Data structure for resolving 64bit keys by string (name). + */ + +#ifndef FLECS_NAME_INDEX_H +#define FLECS_NAME_INDEX_H + +void flecs_name_index_init( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator); + +void flecs_name_index_init_if( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator); + +bool flecs_name_index_is_init( + const ecs_hashmap_t *hm); + +ecs_hashmap_t* flecs_name_index_new( + ecs_world_t *world, + ecs_allocator_t *allocator); + +void flecs_name_index_fini( + ecs_hashmap_t *map); + +void flecs_name_index_free( + ecs_hashmap_t *map); + +ecs_hashmap_t* flecs_name_index_copy( + ecs_hashmap_t *dst); + +ecs_hashed_string_t flecs_get_hashed_string( + const char *name, + ecs_size_t length, + uint64_t hash); + +const uint64_t* flecs_name_index_find_ptr( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash); + +uint64_t flecs_name_index_find( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash); + +void flecs_name_index_ensure( + ecs_hashmap_t *map, + uint64_t id, + const char *name, + ecs_size_t length, + uint64_t hash); + +void flecs_name_index_remove( + ecs_hashmap_t *map, + uint64_t id, + uint64_t hash); + +void flecs_name_index_update_name( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash, + const char *name); + +#endif + + + +//////////////////////////////////////////////////////////////////////////////// +//// Bootstrap API +//////////////////////////////////////////////////////////////////////////////// + +/* Bootstrap world */ +void flecs_bootstrap( + ecs_world_t *world); + +#define flecs_bootstrap_component(world, id_)\ + ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\ + .type.size = sizeof(id_),\ + .type.alignment = ECS_ALIGNOF(id_)\ + }); + +#define flecs_bootstrap_tag(world, name)\ + ecs_ensure(world, name);\ + ecs_add_id(world, name, EcsFinal);\ + ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ + ecs_set(world, name, EcsComponent, {.size = 0});\ + ecs_set_name(world, name, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ + ecs_set_symbol(world, name, #name) + +/* Bootstrap functions for other parts in the code */ +void flecs_bootstrap_hierarchy(ecs_world_t *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 flecs_add_flag( + ecs_world_t *world, + ecs_entity_t entity, + uint32_t flag); + +void flecs_record_add_flag( + ecs_record_t *record, + uint32_t flag); + +ecs_entity_t flecs_get_oneof( + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_notify_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_type_t *diff); + +void flecs_notify_on_set( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *type, + bool owned); + +int32_t flecs_relation_depth( + const ecs_world_t *world, + ecs_entity_t r, + const ecs_table_t *table); + +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count); + +void* flecs_get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_id_record_t *table_index, + int32_t recur_depth); + +void flecs_invoke_hook( + ecs_world_t *world, + ecs_table_t *table, + int32_t count, + int32_t row, + ecs_entity_t *entities, + void *ptr, + ecs_id_t id, + const ecs_type_info_t *ti, + ecs_entity_t event, + ecs_iter_action_t hook); + +//////////////////////////////////////////////////////////////////////////////// +//// Query API +//////////////////////////////////////////////////////////////////////////////// + +/* Match table with term */ +bool flecs_term_match_table( + ecs_world_t *world, + const ecs_term_t *term, + const ecs_table_t *table, + ecs_id_t *id_out, + int32_t *column_out, + ecs_entity_t *subject_out, + int32_t *match_indices, + bool first, + ecs_flags32_t iter_flags); + +/* Match table with filter */ +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_id_t *ids, + int32_t *columns, + ecs_entity_t *sources, + int32_t *match_indices, + int32_t *matches_left, + bool first, + int32_t skip_term, + ecs_flags32_t iter_flags); + +ecs_iter_t flecs_filter_iter_w_flags( + const ecs_world_t *stage, + const ecs_filter_t *filter, + ecs_flags32_t flags); + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event); + +ecs_id_t flecs_to_public_id( + ecs_id_t id); + +ecs_id_t flecs_from_public_id( + ecs_world_t *world, + ecs_id_t id); + +void flecs_filter_apply_iter_flags( + ecs_iter_t *it, + const ecs_filter_t *filter); + +//////////////////////////////////////////////////////////////////////////////// +//// Safe(r) integer casting +//////////////////////////////////////////////////////////////////////////////// + +#define FLECS_CONVERSION_ERR(T, value)\ + "illegal conversion from value " #value " to type " #T + +#define flecs_signed_char__ (CHAR_MIN < 0) +#define flecs_signed_short__ true +#define flecs_signed_int__ true +#define flecs_signed_long__ true +#define flecs_signed_size_t__ false +#define flecs_signed_int8_t__ true +#define flecs_signed_int16_t__ true +#define flecs_signed_int32_t__ true +#define flecs_signed_int64_t__ true +#define flecs_signed_intptr_t__ true +#define flecs_signed_uint8_t__ false +#define flecs_signed_uint16_t__ false +#define flecs_signed_uint32_t__ false +#define flecs_signed_uint64_t__ false +#define flecs_signed_uintptr_t__ false +#define flecs_signed_ecs_size_t__ true +#define flecs_signed_ecs_entity_t__ false + +uint64_t flecs_ito_( + size_t dst_size, + bool dst_signed, + bool lt_zero, + uint64_t value, + const char *err); + +#ifndef FLECS_NDEBUG +#define flecs_ito(T, value)\ + (T)flecs_ito_(\ + sizeof(T),\ + flecs_signed_##T##__,\ + (value) < 0,\ + (uint64_t)(value),\ + FLECS_CONVERSION_ERR(T, (value))) + +#define flecs_uto(T, value)\ + (T)flecs_ito_(\ + sizeof(T),\ + flecs_signed_##T##__,\ + false,\ + (uint64_t)(value),\ + FLECS_CONVERSION_ERR(T, (value))) +#else +#define flecs_ito(T, value) (T)(value) +#define flecs_uto(T, value) (T)(value) +#endif + +#define flecs_itosize(value) flecs_ito(size_t, (value)) +#define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) +#define flecs_itoi16(value) flecs_ito(int16_t, (value)) +#define flecs_itoi32(value) flecs_ito(int32_t, (value)) + +//////////////////////////////////////////////////////////////////////////////// +//// Entity filter +//////////////////////////////////////////////////////////////////////////////// + +void flecs_entity_filter_init( + ecs_world_t *world, + ecs_entity_filter_t **entity_filter, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_id_t *ids, + int32_t *columns); + +void flecs_entity_filter_fini( + ecs_world_t *world, + ecs_entity_filter_t *entity_filter); + +int flecs_entity_filter_next( + ecs_entity_filter_iter_t *it); + +//////////////////////////////////////////////////////////////////////////////// +//// Utilities +//////////////////////////////////////////////////////////////////////////////// + +uint64_t flecs_hash( + const void *data, + ecs_size_t length); + +uint64_t flecs_wyhash( + const void *data, + ecs_size_t length); + +/* Get next power of 2 */ +int32_t flecs_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 flecs_to_row( + uint64_t value); + +/* Get 64bit integer from ecs_record_t */ +uint64_t flecs_from_row( + ecs_record_t record); + +/* Convert a symbol name to an entity name by removing the prefix */ +const char* flecs_name_from_symbol( + ecs_world_t *world, + const char *type_name); + +/* Compare function for entity ids */ +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); + +bool flecs_name_is_id( + const char *name); + +ecs_entity_t flecs_name_to_id( + const ecs_world_t *world, + const char *name); + +/* Convert floating point to string */ +char * ecs_ftoa( + double f, + char * buf, + int precision); + +uint64_t flecs_string_hash( + const void *ptr); + +void flecs_table_hashmap_init( + ecs_world_t *world, + ecs_hashmap_t *hm); + +void flecs_colorize_buf( + char *msg, + bool enable_colors, + ecs_strbuf_t *buf); + +bool flecs_isident( + char ch); + +int32_t flecs_search_w_idr( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out, + ecs_id_record_t *idr); + +int32_t flecs_search_relation_w_idr( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out, + ecs_id_record_t *idr); + +bool flecs_type_can_inherit_id( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_id_record_t *idr, + ecs_id_t id); + +#endif + + +/* -- Identifier Component -- */ +static ECS_DTOR(EcsIdentifier, ptr, { + ecs_os_strset(&ptr->value, NULL); +}) + +static ECS_COPY(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, src->value); + dst->hash = src->hash; + dst->length = src->length; + dst->index_hash = src->index_hash; + dst->index = src->index; +}) + +static ECS_MOVE(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, NULL); + dst->value = src->value; + dst->hash = src->hash; + dst->length = src->length; + dst->index_hash = src->index_hash; + dst->index = src->index; + + src->value = NULL; + src->hash = 0; + src->index_hash = 0; + src->index = 0; + src->length = 0; +}) + +static +void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { + EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 1); + + ecs_world_t *world = it->real_world; + ecs_entity_t evt = it->event; + ecs_id_t evt_id = it->event_id; + ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ + ecs_id_t pair = ecs_childof(0); + ecs_hashmap_t *index = NULL; + + if (kind == EcsSymbol) { + index = &world->symbols; + } else if (kind == EcsAlias) { + index = &world->aliases; + } else if (kind == EcsName) { + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); + ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); + + if (evt == EcsOnSet) { + index = flecs_id_name_index_ensure(world, pair); + } else { + index = flecs_id_name_index_get(world, pair); + } + } + + int i, count = it->count; + + for (i = 0; i < count; i ++) { + EcsIdentifier *cur = &ptr[i]; + uint64_t hash; + ecs_size_t len; + const char *name = cur->value; + + if (cur->index && cur->index != index) { + /* If index doesn't match up, the value must have been copied from + * another entity, so reset index & cached index hash */ + cur->index = NULL; + cur->index_hash = 0; + } + + if (cur->value && (evt == EcsOnSet)) { + len = cur->length = ecs_os_strlen(name); + hash = cur->hash = flecs_hash(name, len); + } else { + len = cur->length = 0; + hash = cur->hash = 0; + cur->index = NULL; + } + + if (index) { + uint64_t index_hash = cur->index_hash; + ecs_entity_t e = it->entities[i]; + + if (hash != index_hash) { + if (index_hash) { + flecs_name_index_remove(index, e, index_hash); + } + if (hash) { + flecs_name_index_ensure(index, e, name, len, hash); + cur->index_hash = hash; + cur->index = index; + } + } else { + /* Name didn't change, but the string could have been + * reallocated. Make sure name index points to correct string */ + flecs_name_index_update_name(index, e, hash, name); + } + } + } +} + + +/* -- Poly component -- */ + +static ECS_COPY(EcsPoly, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); +}) + +static ECS_MOVE(EcsPoly, dst, src, { + if (dst->poly && (dst->poly != src->poly)) { + ecs_poly_dtor_t *dtor = ecs_get_dtor(dst->poly); + ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); + dtor[0](dst->poly); + } + + dst->poly = src->poly; + src->poly = NULL; +}) + +static ECS_DTOR(EcsPoly, ptr, { + if (ptr->poly) { + ecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly); + ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); + dtor[0](ptr->poly); + } +}) + + +/* -- Builtin triggers -- */ + +static +void flecs_assert_relation_unused( + ecs_world_t *world, + ecs_entity_t rel, + ecs_entity_t property) +{ + if (world->flags & (EcsWorldInit|EcsWorldFini)) { + return; + } + + ecs_vec_t *marked_ids = &world->store.marked_ids; + int32_t i, count = ecs_vec_count(marked_ids); + for (i = 0; i < count; i ++) { + ecs_marked_id_t *mid = ecs_vec_get_t(marked_ids, ecs_marked_id_t, i); + if (mid->id == ecs_pair(rel, EcsWildcard)) { + /* If id is being cleaned up, no need to throw error as tables will + * be cleaned up */ + return; + } + } + + bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); + if (property != EcsUnion) { + in_use |= ecs_id_in_use(world, rel); + } + if (in_use) { + char *r_str = ecs_get_fullpath(world, rel); + char *p_str = ecs_get_fullpath(world, property); + + ecs_throw(ECS_ID_IN_USE, + "cannot change property '%s' for relationship '%s': already in use", + p_str, r_str); + + ecs_os_free(r_str); + ecs_os_free(p_str); + } + +error: + return; +} + +static +bool flecs_set_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) +{ + if (!(idr->flags & flag)) { + idr->flags |= flag; + return true; + } + return false; +} + +static +bool flecs_unset_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) +{ + if ((idr->flags & flag)) { + idr->flags &= ~flag; + return true; + } + return false; +} + +static +void flecs_register_id_flag_for_relation( + ecs_iter_t *it, + ecs_entity_t prop, + ecs_flags32_t flag, + ecs_flags32_t not_flag, + ecs_flags32_t entity_flag) +{ + ecs_world_t *world = it->world; + ecs_entity_t event = it->event; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + bool changed = false; + + if (event == EcsOnAdd) { + ecs_id_record_t *idr = flecs_id_record_ensure(world, e); + changed |= flecs_set_id_flag(idr, flag); + idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); + do { + changed |= flecs_set_id_flag(idr, flag); + } while ((idr = idr->first.next)); + if (entity_flag) flecs_add_flag(world, e, entity_flag); + } else if (event == EcsOnRemove) { + ecs_id_record_t *idr = flecs_id_record_get(world, e); + if (idr) changed |= flecs_unset_id_flag(idr, not_flag); + idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); + if (idr) { + do { + changed |= flecs_unset_id_flag(idr, not_flag); + } while ((idr = idr->first.next)); + } + } + + if (changed) { + flecs_assert_relation_unused(world, e, prop); + } + } +} + +static +void flecs_register_final(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + if (flecs_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) { + char *e_str = ecs_get_fullpath(world, e); + ecs_throw(ECS_ID_IN_USE, + "cannot change property 'Final' for '%s': already inherited from", + e_str); + ecs_os_free(e_str); + error: + continue; + } + } +} + +static +void flecs_register_on_delete(ecs_iter_t *it) { + ecs_id_t id = ecs_field_id(it, 1); + flecs_register_id_flag_for_relation(it, EcsOnDelete, + ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteMask, + EcsEntityIsId); +} + +static +void flecs_register_on_delete_object(ecs_iter_t *it) { + ecs_id_t id = ecs_field_id(it, 1); + flecs_register_id_flag_for_relation(it, EcsOnDeleteTarget, + ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteObjectMask, + EcsEntityIsId); +} + +static +void flecs_register_traversable(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdTraversable, + EcsIdTraversable, 0); +} + +static +void flecs_register_tag(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsTag, EcsIdTag, ~EcsIdTag, 0); + + /* Ensure that all id records for tag have type info set to NULL */ + ecs_world_t *world = it->real_world; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (it->event == EcsOnAdd) { + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(e, EcsWildcard)); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + do { + if (idr->type_info != NULL) { + flecs_assert_relation_unused(world, e, EcsTag); + } + idr->type_info = NULL; + } while ((idr = idr->first.next)); + } + } +} + +static +void flecs_register_exclusive(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, + EcsIdExclusive, 0); +} + +static +void flecs_register_dont_inherit(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsDontInherit, + EcsIdDontInherit, EcsIdDontInherit, 0); +} + +static +void flecs_register_always_override(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsAlwaysOverride, + EcsIdAlwaysOverride, EcsIdAlwaysOverride, 0); +} + +static +void flecs_register_with(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); +} + +static +void flecs_register_union(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); +} + +static +void flecs_register_slot_of(ecs_iter_t *it) { + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_add_id(it->world, it->entities[i], EcsUnion); + } +} + +static +void flecs_on_symmetric_add_remove(ecs_iter_t *it) { + ecs_entity_t pair = ecs_field_id(it, 1); + + if (!ECS_HAS_ID_FLAG(pair, PAIR)) { + /* If relationship was not added as a pair, there's nothing to do */ + return; + } + + ecs_world_t *world = it->world; + ecs_entity_t rel = ECS_PAIR_FIRST(pair); + ecs_entity_t obj = ecs_pair_second(world, pair); + ecs_entity_t event = it->event; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t subj = it->entities[i]; + if (event == EcsOnAdd) { + if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { + ecs_add_pair(it->world, obj, rel, subj); + } + } else { + if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { + ecs_remove_pair(it->world, obj, rel, subj); + } + } + } +} + +static +void flecs_register_symmetric(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t r = it->entities[i]; + flecs_assert_relation_unused(world, r, EcsSymmetric); + + /* Create observer that adds the reverse relationship when R(X, Y) is + * added, or remove the reverse relationship when R(X, Y) is removed. */ + ecs_observer(world, { + .entity = ecs_entity(world, {.add = {ecs_childof(r)}}), + .filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, + .callback = flecs_on_symmetric_add_remove, + .events = {EcsOnAdd, EcsOnRemove} + }); + } +} + +static +void flecs_on_component(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsComponent *c = ecs_field(it, EcsComponent, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + uint32_t component_id = (uint32_t)e; /* Strip generation */ + ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE, + "component id must be smaller than %u", ECS_MAX_COMPONENT_ID); + (void)component_id; + + if (it->event == EcsOnSet) { + if (flecs_type_info_init_id( + world, e, c[i].size, c[i].alignment, NULL)) + { + flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); + } + } else if (it->event == EcsOnRemove) { + flecs_type_info_free(world, e); + } + } +} + +static +void flecs_ensure_module_tag(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (parent) { + ecs_add_id(world, parent, EcsModule); + } + } +} + +/* -- Iterable mixins -- */ + +static +void flecs_on_event_iterable_init( + const ecs_world_t *world, + const ecs_poly_t *poly, /* Observable */ + ecs_iter_t *it, + ecs_term_t *filter) +{ + ecs_iter_poly(world, poly, it, filter); + it->event_id = filter->id; +} + +/* -- Bootstrapping -- */ + +#define flecs_bootstrap_builtin_t(world, table, name)\ + flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) + +static +void flecs_bootstrap_builtin( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_column_t *columns = table->data.columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *record = flecs_entities_ensure(world, entity); + record->table = table; + + int32_t index = flecs_table_append(world, table, entity, false, false); + record->row = ECS_ROW_TO_RECORD(index, 0); + + EcsComponent *component = ecs_vec_first(&columns[0].data); + component[index].size = size; + component[index].alignment = alignment; + + const char *name = &symbol[3]; /* Strip 'Ecs' */ + ecs_size_t symbol_length = ecs_os_strlen(symbol); + ecs_size_t name_length = symbol_length - 3; + + EcsIdentifier *name_col = ecs_vec_first(&columns[1].data); + uint64_t name_hash = flecs_hash(name, name_length); + name_col[index].value = ecs_os_strdup(name); + name_col[index].length = name_length; + name_col[index].hash = name_hash; + name_col[index].index_hash = 0; + name_col[index].index = table->_->name_index; + flecs_name_index_ensure( + table->_->name_index, entity, name, name_length, name_hash); + + EcsIdentifier *symbol_col = ecs_vec_first(&columns[2].data); + symbol_col[index].value = ecs_os_strdup(symbol); + symbol_col[index].length = symbol_length; + symbol_col[index].hash = flecs_hash(symbol, symbol_length); + symbol_col[index].index_hash = 0; + symbol_col[index].index = NULL; +} + +/** 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 EcsIdentifier components, which haven't + * been created yet */ +static +ecs_table_t* flecs_bootstrap_component_table( + ecs_world_t *world) +{ + /* Before creating table, manually set flags for ChildOf/Identifier, as this + * can no longer be done after they are in use. */ + ecs_id_record_t *idr = flecs_id_record_ensure(world, EcsChildOf); + idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | + EcsIdTraversable | EcsIdTag; + + /* Initialize id records cached on world */ + world->idr_childof_wildcard = flecs_id_record_ensure(world, + ecs_pair(EcsChildOf, EcsWildcard)); + world->idr_childof_wildcard->flags |= EcsIdOnDeleteObjectDelete | + EcsIdDontInherit | EcsIdTraversable | EcsIdTag | EcsIdExclusive; + idr = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard)); + idr->flags |= EcsIdDontInherit; + world->idr_identifier_name = + flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); + world->idr_childof_0 = flecs_id_record_ensure(world, + ecs_pair(EcsChildOf, 0)); + + ecs_id_t ids[] = { + ecs_id(EcsComponent), + EcsFinal, + ecs_pair_t(EcsIdentifier, EcsName), + ecs_pair_t(EcsIdentifier, EcsSymbol), + ecs_pair(EcsChildOf, EcsFlecsCore), + ecs_pair(EcsOnDelete, EcsPanic) + }; + + ecs_type_t array = { + .array = ids, + .count = 6 + }; + + ecs_table_t *result = flecs_table_find_or_create(world, &array); + ecs_data_t *data = &result->data; + + /* Preallocate enough memory for initial components */ + ecs_allocator_t *a = &world->allocator; + ecs_vec_init_t(a, &data->entities, ecs_entity_t, EcsFirstUserComponentId); + ecs_vec_init_t(a, &data->columns[0].data, EcsComponent, EcsFirstUserComponentId); + ecs_vec_init_t(a, &data->columns[1].data, EcsIdentifier, EcsFirstUserComponentId); + ecs_vec_init_t(a, &data->columns[2].data, EcsIdentifier, EcsFirstUserComponentId); + + return result; +} + +static +void flecs_bootstrap_entity( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + ecs_entity_t parent) +{ + char symbol[256]; + ecs_os_strcpy(symbol, "flecs.core."); + ecs_os_strcat(symbol, name); + + ecs_ensure(world, id); + ecs_add_pair(world, id, EcsChildOf, parent); + ecs_set_name(world, id, name); + ecs_set_symbol(world, id, symbol); + + ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!parent || parent == EcsFlecsCore) { + ecs_assert(ecs_lookup_fullpath(world, name) == id, + ECS_INTERNAL_ERROR, NULL); + } +} + +void flecs_bootstrap( + ecs_world_t *world) +{ + ecs_log_push(); + + ecs_set_name_prefix(world, "Ecs"); + + /* Ensure builtin ids are alive */ + ecs_ensure(world, ecs_id(EcsComponent)); + ecs_ensure(world, EcsFinal); + ecs_ensure(world, ecs_id(EcsIdentifier)); + ecs_ensure(world, EcsName); + ecs_ensure(world, EcsSymbol); + ecs_ensure(world, EcsAlias); + ecs_ensure(world, EcsChildOf); + ecs_ensure(world, EcsFlecs); + ecs_ensure(world, EcsFlecsCore); + ecs_ensure(world, EcsOnAdd); + ecs_ensure(world, EcsOnRemove); + ecs_ensure(world, EcsOnSet); + ecs_ensure(world, EcsUnSet); + ecs_ensure(world, EcsOnDelete); + ecs_ensure(world, EcsPanic); + ecs_ensure(world, EcsFlag); + ecs_ensure(world, EcsIsA); + ecs_ensure(world, EcsWildcard); + ecs_ensure(world, EcsAny); + ecs_ensure(world, EcsTag); + + /* Register type information for builtin components */ + flecs_type_info_init(world, EcsComponent, { + .ctor = ecs_default_ctor, + .on_set = flecs_on_component, + .on_remove = flecs_on_component + }); + + flecs_type_info_init(world, EcsIdentifier, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsIdentifier), + .copy = ecs_copy(EcsIdentifier), + .move = ecs_move(EcsIdentifier), + .on_set = ecs_on_set(EcsIdentifier), + .on_remove = ecs_on_set(EcsIdentifier) + }); + + flecs_type_info_init(world, EcsPoly, { + .ctor = ecs_default_ctor, + .copy = ecs_copy(EcsPoly), + .move = ecs_move(EcsPoly), + .dtor = ecs_dtor(EcsPoly) + }); + + flecs_type_info_init(world, EcsIterable, { 0 }); + flecs_type_info_init(world, EcsTarget, { 0 }); + + /* Create and cache often used id records on world */ + flecs_init_id_records(world); + + /* Create table for builtin components. This table temporarily stores the + * entities associated with builtin components, until they get moved to + * other tables once properties are added (see below) */ + ecs_table_t *table = flecs_bootstrap_component_table(world); + assert(table != NULL); + + /* Bootstrap builtin components */ + flecs_bootstrap_builtin_t(world, table, EcsIdentifier); + flecs_bootstrap_builtin_t(world, table, EcsComponent); + flecs_bootstrap_builtin_t(world, table, EcsIterable); + flecs_bootstrap_builtin_t(world, table, EcsPoly); + flecs_bootstrap_builtin_t(world, table, EcsTarget); + + /* Initialize default entity id range */ + world->info.last_component_id = EcsFirstUserComponentId; + flecs_entities_max_id(world) = EcsFirstUserEntityId; + world->info.min_id = 0; + world->info.max_id = 0; + + /* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ + ecs_set(world, EcsOnAdd, EcsIterable, { .init = flecs_on_event_iterable_init }); + ecs_set(world, EcsOnSet, EcsIterable, { .init = flecs_on_event_iterable_init }); + + /* Register observer for tag property before adding EcsTag */ + ecs_observer(world, { + .entity = ecs_entity(world, {.add = { ecs_childof(EcsFlecsInternals)}}), + .filter.terms[0] = { .id = EcsTag, .src.flags = EcsSelf }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_tag, + .yield_existing = true + }); + + /* Populate core module */ + ecs_set_scope(world, EcsFlecsCore); + + flecs_bootstrap_tag(world, EcsName); + flecs_bootstrap_tag(world, EcsSymbol); + flecs_bootstrap_tag(world, EcsAlias); + + flecs_bootstrap_tag(world, EcsQuery); + flecs_bootstrap_tag(world, EcsObserver); + + flecs_bootstrap_tag(world, EcsModule); + flecs_bootstrap_tag(world, EcsPrivate); + flecs_bootstrap_tag(world, EcsPrefab); + flecs_bootstrap_tag(world, EcsSlotOf); + flecs_bootstrap_tag(world, EcsDisabled); + flecs_bootstrap_tag(world, EcsEmpty); + + /* Initialize builtin modules */ + ecs_set_name(world, EcsFlecs, "flecs"); + ecs_add_id(world, EcsFlecs, EcsModule); + ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); + + ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); + ecs_set_name(world, EcsFlecsCore, "core"); + ecs_add_id(world, EcsFlecsCore, EcsModule); + + ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); + ecs_set_name(world, EcsFlecsInternals, "internals"); + ecs_add_id(world, EcsFlecsInternals, EcsModule); + + /* Self check */ + ecs_record_t *r = flecs_entities_get(world, EcsFlecs); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->row & EcsEntityIsTraversable, ECS_INTERNAL_ERROR, NULL); + (void)r; + + /* Initialize builtin entities */ + flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsThis, "this", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); + + /* Component/relationship properties */ + flecs_bootstrap_tag(world, EcsTransitive); + flecs_bootstrap_tag(world, EcsReflexive); + flecs_bootstrap_tag(world, EcsSymmetric); + flecs_bootstrap_tag(world, EcsFinal); + flecs_bootstrap_tag(world, EcsDontInherit); + flecs_bootstrap_tag(world, EcsAlwaysOverride); + flecs_bootstrap_tag(world, EcsTag); + flecs_bootstrap_tag(world, EcsUnion); + flecs_bootstrap_tag(world, EcsExclusive); + flecs_bootstrap_tag(world, EcsAcyclic); + flecs_bootstrap_tag(world, EcsTraversable); + flecs_bootstrap_tag(world, EcsWith); + flecs_bootstrap_tag(world, EcsOneOf); + + flecs_bootstrap_tag(world, EcsOnDelete); + flecs_bootstrap_tag(world, EcsOnDeleteTarget); + flecs_bootstrap_tag(world, EcsRemove); + flecs_bootstrap_tag(world, EcsDelete); + flecs_bootstrap_tag(world, EcsPanic); + + flecs_bootstrap_tag(world, EcsFlatten); + flecs_bootstrap_tag(world, EcsDefaultChildComponent); + + /* Builtin predicates */ + flecs_bootstrap_tag(world, EcsPredEq); + flecs_bootstrap_tag(world, EcsPredMatch); + flecs_bootstrap_tag(world, EcsPredLookup); + flecs_bootstrap_tag(world, EcsScopeOpen); + flecs_bootstrap_tag(world, EcsScopeClose); + + /* Builtin relationships */ + flecs_bootstrap_tag(world, EcsIsA); + flecs_bootstrap_tag(world, EcsChildOf); + flecs_bootstrap_tag(world, EcsDependsOn); + + /* Builtin events */ + flecs_bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); + + /* Tag relationships (relationships that should never have data) */ + ecs_add_id(world, EcsIsA, EcsTag); + ecs_add_id(world, EcsChildOf, EcsTag); + ecs_add_id(world, EcsSlotOf, EcsTag); + ecs_add_id(world, EcsDependsOn, EcsTag); + ecs_add_id(world, EcsFlatten, EcsTag); + ecs_add_id(world, EcsDefaultChildComponent, EcsTag); + ecs_add_id(world, EcsUnion, EcsTag); + ecs_add_id(world, EcsFlag, EcsTag); + ecs_add_id(world, EcsWith, EcsTag); + + /* Exclusive properties */ + ecs_add_id(world, EcsChildOf, EcsExclusive); + ecs_add_id(world, EcsOnDelete, EcsExclusive); + ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); + ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); + + /* Sync properties of ChildOf and Identifier with bootstrapped flags */ + ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); + ecs_add_id(world, EcsChildOf, EcsAcyclic); + ecs_add_id(world, EcsChildOf, EcsTraversable); + ecs_add_id(world, EcsChildOf, EcsDontInherit); + ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); + + /* Create triggers in internals scope */ + ecs_set_scope(world, EcsFlecsInternals); + + /* Term used to also match prefabs */ + ecs_term_t match_prefab = { + .id = EcsPrefab, + .oper = EcsOptional, + .src.flags = EcsSelf + }; + + /* Register observers for components/relationship properties. Most observers + * set flags on an id record when a property is added to a component, which + * allows for quick property testing in various operations. */ + ecs_observer(world, { + .filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = flecs_register_final + }); + + ecs_observer(world, { + .filter.terms = { + { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete + }); + + ecs_observer(world, { + .filter.terms = { + { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete_object + }); + + ecs_observer(world, { + .filter.terms = { + { .id = EcsTraversable, .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_traversable + }); + + ecs_observer(world, { + .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_exclusive + }); + + ecs_observer(world, { + .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = flecs_register_symmetric + }); + + ecs_observer(world, { + .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = flecs_register_dont_inherit + }); + + ecs_observer(world, { + .filter.terms = {{ .id = EcsAlwaysOverride, .src.flags = EcsSelf } }, + .events = {EcsOnAdd}, + .callback = flecs_register_always_override + }); + + ecs_observer(world, { + .filter.terms = { + { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd}, + .callback = flecs_register_with + }); + + ecs_observer(world, { + .filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = flecs_register_union + }); + + /* Entities used as slot are marked as exclusive to ensure a slot can always + * only point to a single entity. */ + ecs_observer(world, { + .filter.terms = { + { .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd}, + .callback = flecs_register_slot_of + }); + + /* Define observer to make sure that adding a module to a child entity also + * adds it to the parent. */ + ecs_observer(world, { + .filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab}, + .events = {EcsOnAdd}, + .callback = flecs_ensure_module_tag + }); + + /* Set scope back to flecs core */ + ecs_set_scope(world, EcsFlecsCore); + + /* Traversable relationships are always acyclic */ + ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); + + /* Transitive relationships are always Traversable */ + ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); + + /* DontInherit components */ + ecs_add_id(world, EcsPrefab, EcsDontInherit); + ecs_add_id(world, ecs_id(EcsComponent), EcsDontInherit); + ecs_add_id(world, EcsOnDelete, EcsDontInherit); + + /* Acyclic/Traversable components */ + ecs_add_id(world, EcsIsA, EcsTraversable); + ecs_add_id(world, EcsDependsOn, EcsTraversable); + ecs_add_id(world, EcsWith, EcsAcyclic); + + /* Transitive relationships */ + ecs_add_id(world, EcsIsA, EcsTransitive); + ecs_add_id(world, EcsIsA, EcsReflexive); + + /* Exclusive properties */ + ecs_add_id(world, EcsSlotOf, EcsExclusive); + ecs_add_id(world, EcsOneOf, EcsExclusive); + ecs_add_id(world, EcsFlatten, EcsExclusive); + + /* Private properties */ + ecs_add_id(world, ecs_id(EcsPoly), EcsPrivate); + ecs_add_id(world, ecs_id(EcsIdentifier), EcsPrivate); + ecs_add_id(world, EcsChildOf, EcsPrivate); + ecs_add_id(world, EcsIsA, EcsPrivate); + + /* Run bootstrap functions for other parts of the code */ + flecs_bootstrap_hierarchy(world); + + ecs_set_scope(world, 0); + + ecs_set_name_prefix(world, NULL); + + ecs_assert(world->idr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->idr_isa_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_log_pop(); +} + +/** + * @file entity.c + * @brief Entity API. + * + * This file contains the implementation for the entity API, which includes + * creating/deleting entities, adding/removing/setting components, instantiating + * prefabs, and several other APIs for retrieving entity data. + * + * The file also contains the implementation of the command buffer, which is + * located here so it can call functions private to the compilation unit. + */ + +#include + +static +const ecs_entity_t* flecs_bulk_new( + ecs_world_t *world, + ecs_table_t *table, + const ecs_entity_t *entities, + ecs_type_t *component_ids, + int32_t count, + void **c_info, + bool move, + int32_t *row_out, + ecs_table_diff_t *diff); + +typedef struct { + const ecs_type_info_t *ti; + void *ptr; +} flecs_component_ptr_t; + +static +flecs_component_ptr_t flecs_get_component_w_index( + ecs_table_t *table, + int32_t column_index, + int32_t row) +{ + ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); + ecs_column_t *column = &table->data.columns[column_index]; + return (flecs_component_ptr_t){ + .ti = column->ti, + .ptr = ecs_vec_get(&column->data, column->size, row) + }; +error: + return (flecs_component_ptr_t){0}; +} + +static +flecs_component_ptr_t flecs_get_component_ptr( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_id_t id) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_table_record_t *tr = flecs_table_record_get(world, table, id); + if (!tr || (tr->column == -1)) { + ecs_check(tr == NULL, ECS_NOT_A_COMPONENT, NULL); + return (flecs_component_ptr_t){0}; + } + + return flecs_get_component_w_index(table, tr->column, row); +error: + return (flecs_component_ptr_t){0}; +} + +static +void* flecs_get_component( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_id_t id) +{ + return flecs_get_component_ptr(world, table, row, id).ptr; +} + +void* flecs_get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_id_record_t *table_index, + int32_t recur_depth) +{ + /* Cycle detected in IsA relationship */ + ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); + + /* Table (and thus entity) does not have component, look for base */ + if (!(table->flags & EcsTableHasIsA)) { + return NULL; + } + + /* Exclude Name */ + if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { + return NULL; + } + + /* Table should always be in the table index for (IsA, *), otherwise the + * HasBase flag should not have been set */ + ecs_table_record_t *tr_isa = flecs_id_record_get_table( + world->idr_isa_wildcard, table); + ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t i = tr_isa->index, end = tr_isa->count + tr_isa->index; + void *ptr = NULL; + + do { + ecs_id_t pair = ids[i ++]; + ecs_entity_t base = ecs_pair_second(world, pair); + + ecs_record_t *r = flecs_entities_get(world, base); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + table = r->table; + if (!table) { + continue; + } + + const ecs_table_record_t *tr = + flecs_id_record_get_table(table_index, table); + if (!tr || tr->column == -1) { + ptr = flecs_get_base_component(world, table, id, table_index, + recur_depth + 1); + } else { + int32_t row = ECS_RECORD_TO_ROW(r->row); + ptr = flecs_get_component_w_index(table, tr->column, row).ptr; + } + } while (!ptr && (i < end)); + + return ptr; +error: + return NULL; +} + +static +void flecs_instantiate_slot( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance, + ecs_entity_t slot_of, + ecs_entity_t slot, + ecs_entity_t child) +{ + if (base == slot_of) { + /* Instance inherits from slot_of, add slot to instance */ + ecs_add_pair(world, instance, slot, child); + } else { + /* Slot is registered for other prefab, travel hierarchy + * upwards to find instance that inherits from slot_of */ + ecs_entity_t parent = instance; + int32_t depth = 0; + do { + if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { + const char *name = ecs_get_name(world, slot); + if (name == NULL) { + char *slot_of_str = ecs_get_fullpath(world, slot_of); + ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " + "slot (slots must be named)", slot_of_str); + ecs_os_free(slot_of_str); + return; + } + + /* The 'slot' variable is currently pointing to a child (or + * grandchild) of the current base. Find the original slot by + * looking it up under the prefab it was registered. */ + if (depth == 0) { + /* If the current instance is an instance of slot_of, just + * lookup the slot by name, which is faster than having to + * create a relative path. */ + slot = ecs_lookup_child(world, slot_of, name); + } else { + /* If the slot is more than one level away from the slot_of + * parent, use a relative path to find the slot */ + char *path = ecs_get_path_w_sep(world, parent, child, ".", + NULL); + slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", + NULL, false); + ecs_os_free(path); + } + + if (slot == 0) { + char *slot_of_str = ecs_get_fullpath(world, slot_of); + char *slot_str = ecs_get_fullpath(world, slot); + ecs_throw(ECS_INVALID_OPERATION, + "'%s' is not in hierarchy for slot '%s'", + slot_of_str, slot_str); + ecs_os_free(slot_of_str); + ecs_os_free(slot_str); + } + + ecs_add_pair(world, parent, slot, child); + break; + } + + depth ++; + } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); + + if (parent == 0) { + char *slot_of_str = ecs_get_fullpath(world, slot_of); + char *slot_str = ecs_get_fullpath(world, slot); + ecs_throw(ECS_INVALID_OPERATION, + "'%s' is not in hierarchy for slot '%s'", + slot_of_str, slot_str); + ecs_os_free(slot_of_str); + ecs_os_free(slot_str); + } + } + +error: + return; +} + +static +ecs_table_t* flecs_find_table_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_table_diff_builder_t *diff) +{ + ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; + table = flecs_table_traverse_add(world, table, &id, &temp_diff); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_table_diff_build_append_table(world, diff, &temp_diff); + return table; +error: + return NULL; +} + +static +ecs_table_t* flecs_find_table_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_table_diff_builder_t *diff) +{ + ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; + table = flecs_table_traverse_remove(world, table, &id, &temp_diff); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_table_diff_build_append_table(world, diff, &temp_diff); + return table; +error: + return NULL; +} + +static +void flecs_instantiate_children( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_table_t *child_table) +{ + if (!ecs_table_count(child_table)) { + return; + } + + ecs_type_t type = child_table->type; + ecs_data_t *child_data = &child_table->data; + + ecs_entity_t slot_of = 0; + ecs_entity_t *ids = type.array; + int32_t type_count = type.count; + + /* Instantiate child table for each instance */ + + /* Create component array for creating the table */ + ecs_type_t components = { + .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) + }; + + void **component_data = ecs_os_alloca_n(void*, type_count + 1); + + /* 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 j, i, childof_base_index = -1, pos = 0; + for (i = 0; i < type_count; i ++) { + ecs_id_t id = ids[i]; + + /* If id has DontInherit flag don't inherit it, except for the name + * and ChildOf pairs. The name is preserved so applications can lookup + * the instantiated children by name. The ChildOf pair is replaced later + * with the instance parent. */ + if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && + ECS_PAIR_FIRST(id) != EcsChildOf) + { + if (id == EcsUnion) { + /* This should eventually be handled by the DontInherit property + * but right now there is no way to selectively apply it to + * EcsUnion itself: it would also apply to (Union, *) pairs, + * which would make all union relationships uninheritable. + * + * The reason this is explicitly skipped is so that slot + * instances don't all end up with the Union property. */ + continue; + } + ecs_table_record_t *tr = &child_table->_->records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + if (idr->flags & EcsIdDontInherit) { + continue; + } + } + + /* If child is a slot, keep track of which parent to add it to, but + * don't add slot relationship to child of instance. If this is a child + * of a prefab, keep the SlotOf relationship intact. */ + if (!(table->flags & EcsTableIsPrefab)) { + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { + ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); + slot_of = ecs_pair_second(world, id); + 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_RELATION(id, EcsChildOf) && + (ECS_PAIR_SECOND(id) == (uint32_t)base)) { + childof_base_index = pos; + } + + int32_t storage_index = ecs_table_type_to_column_index(child_table, i); + if (storage_index != -1) { + ecs_vec_t *column = &child_data->columns[storage_index].data; + component_data[pos] = ecs_vec_first(column); + } else { + component_data[pos] = NULL; + } + + components.array[pos] = id; + pos ++; + } + + /* Table must contain children of base */ + ecs_assert(childof_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; + component_data[pos] = NULL; + pos ++; + } + + components.count = pos; + + /* Instantiate the prefab child table for each new instance */ + ecs_entity_t *instances = ecs_vec_first(&table->data.entities); + int32_t child_count = ecs_vec_count(&child_data->entities); + bool has_union = child_table->flags & EcsTableHasUnion; + + for (i = row; i < count + row; i ++) { + ecs_entity_t instance = instances[i]; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + ecs_table_t *i_table = NULL; + + /* Replace ChildOf element in the component array with instance id */ + components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); + + /* Find or create table */ + for (j = 0; j < components.count; j ++) { + i_table = flecs_find_table_add( + world, i_table, components.array[j], &diff); + } + + ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(i_table->type.count == components.count, + 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. */ + ecs_entity_t *children = ecs_vec_first(&child_data->entities); + +#ifdef FLECS_DEBUG + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); + } +#else + /* Bit of boilerplate to ensure that we don't get warnings about the + * error label not being used. */ + ecs_check(true, ECS_INVALID_OPERATION, NULL); +#endif + + /* Create children */ + int32_t child_row; + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, + &components, child_count, component_data, false, &child_row, + &table_diff); + flecs_table_diff_builder_fini(world, &diff); + + /* If children have union relationships, initialize */ + if (has_union) { + ecs_table__t *meta = child_table->_; + ecs_assert(meta != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(i_table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t u, u_count = meta->sw_count; + for (u = 0; u < u_count; u ++) { + ecs_switch_t *src_sw = &meta->sw_columns[i]; + ecs_switch_t *dst_sw = &i_table->_->sw_columns[i]; + ecs_vec_t *v_src_values = flecs_switch_values(src_sw); + ecs_vec_t *v_dst_values = flecs_switch_values(dst_sw); + uint64_t *src_values = ecs_vec_first(v_src_values); + uint64_t *dst_values = ecs_vec_first(v_dst_values); + for (j = 0; j < child_count; j ++) { + dst_values[j] = src_values[j]; + } + } + } + + /* If children are slots, add slot relationships to parent */ + if (slot_of) { + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_entity_t i_child = i_children[j]; + flecs_instantiate_slot(world, base, instance, slot_of, + child, i_child); + } + } + + /* If prefab child table has children itself, recursively instantiate */ + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + flecs_instantiate(world, child, i_table, child_row + j, 1); + } + } +error: + return; +} + +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count) +{ + ecs_record_t *record = flecs_entities_get_any(world, base); + ecs_table_t *base_table = record->table; + if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { + /* Don't instantiate children from base entities that aren't prefabs */ + return; + } + + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + flecs_instantiate_children( + world, base, table, row, count, tr->hdr.table); + } + } +} + +static +void flecs_set_union( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *ids) +{ + ecs_id_t *array = ids->array; + int32_t i, id_count = ids->count; + + for (i = 0; i < id_count; i ++) { + ecs_id_t id = array[i]; + + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + if (!idr) { + continue; + } + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t column = tr->index - table->_->sw_offset; + ecs_switch_t *sw = &table->_->sw_columns[column]; + ecs_entity_t union_case = 0; + union_case = ecs_pair_second(world, id); + + int32_t r; + for (r = 0; r < count; r ++) { + flecs_switch_set(sw, row + r, union_case); + } + } + } +} + +static +void flecs_notify_on_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_type_t *added, + ecs_flags32_t flags) +{ + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + + if (added->count) { + ecs_flags32_t table_flags = table->flags; + + if (table_flags & EcsTableHasUnion) { + flecs_set_union(world, table, row, count, added); + } + + if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasTraversable)) { + flecs_emit(world, world, &(ecs_event_desc_t){ + .event = EcsOnAdd, + .ids = added, + .table = table, + .other_table = other_table, + .offset = row, + .count = count, + .observable = world, + .flags = flags + }); + } + } +} + +void flecs_notify_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_type_t *removed) +{ + ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + if (removed->count && (table->flags & + (EcsTableHasOnRemove|EcsTableHasUnSet|EcsTableHasIsA|EcsTableHasTraversable))) + { + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnRemove, + .ids = removed, + .table = table, + .other_table = other_table, + .offset = row, + .count = count, + .observable = world + }); + } +} + +static +void flecs_update_name_index( + ecs_world_t *world, + ecs_table_t *src, + ecs_table_t *dst, + int32_t offset, + int32_t count) +{ + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + if (!(dst->flags & EcsTableHasName)) { + /* If destination table doesn't have a name, we don't need to update the + * name index. Even if the src table had a name, the on_remove hook for + * EcsIdentifier will remove the entity from the index. */ + return; + } + + ecs_hashmap_t *src_index = src->_->name_index; + ecs_hashmap_t *dst_index = dst->_->name_index; + if ((src_index == dst_index) || (!src_index && !dst_index)) { + /* If the name index didn't change, the entity still has the same parent + * so nothing needs to be done. */ + return; + } + + EcsIdentifier *names = ecs_table_get_pair(world, + dst, EcsIdentifier, EcsName, offset); + ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i; + ecs_entity_t *entities = ecs_vec_get_t( + &dst->data.entities, ecs_entity_t, offset); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + EcsIdentifier *name = &names[i]; + + uint64_t index_hash = name->index_hash; + if (index_hash) { + flecs_name_index_remove(src_index, e, index_hash); + } + const char *name_str = name->value; + if (name_str) { + ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); + + flecs_name_index_ensure( + dst_index, e, name_str, name->length, name->hash); + name->index = dst_index; + } + } +} + +static +ecs_record_t* flecs_new_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + ecs_table_diff_t *diff, + bool ctor, + ecs_flags32_t evt_flags) +{ + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t row = flecs_table_append(world, table, entity, ctor, true); + record->table = table; + record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); + + ecs_assert(ecs_vec_count(&table->data.entities) > row, + ECS_INTERNAL_ERROR, NULL); + flecs_notify_on_add(world, table, NULL, row, 1, &diff->added, evt_flags); + + return record; +} + +static +void flecs_move_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *dst_table, + ecs_table_diff_t *diff, + bool ctor, + ecs_flags32_t evt_flags) +{ + ecs_table_t *src_table = record->table; + int32_t src_row = ECS_RECORD_TO_ROW(record->row); + + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vec_count(&src_table->data.entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record == flecs_entities_get(world, entity), + ECS_INTERNAL_ERROR, NULL); + + /* Append new row to destination table */ + int32_t dst_row = flecs_table_append(world, dst_table, entity, + false, false); + + /* Invoke remove actions for removed components */ + flecs_notify_on_remove( + world, src_table, dst_table, src_row, 1, &diff->removed); + + /* Copy entity & components from src_table to dst_table */ + flecs_table_move(world, entity, entity, dst_table, dst_row, + src_table, src_row, ctor); + + /* Update entity index & delete old data after running remove actions */ + record->table = dst_table; + record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); + + flecs_table_delete(world, src_table, src_row, false); + flecs_notify_on_add( + world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); + + flecs_update_name_index(world, src_table, dst_table, dst_row, 1); + +error: + return; +} + +static +void flecs_delete_entity( + ecs_world_t *world, + ecs_record_t *record, + ecs_table_diff_t *diff) +{ + ecs_table_t *table = record->table; + int32_t row = ECS_RECORD_TO_ROW(record->row); + + /* Invoke remove actions before deleting */ + flecs_notify_on_remove(world, table, NULL, row, 1, &diff->removed); + flecs_table_delete(world, table, 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 +void flecs_update_component_monitor_w_array( + ecs_world_t *world, + ecs_type_t *ids) +{ + if (!ids) { + return; + } + + int i; + for (i = 0; i < ids->count; i ++) { + ecs_entity_t id = ids->array[i]; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + flecs_monitor_mark_dirty(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); + } + + flecs_monitor_mark_dirty(world, id); + } +} + +static +void flecs_update_component_monitors( + ecs_world_t *world, + ecs_type_t *added, + ecs_type_t *removed) +{ + flecs_update_component_monitor_w_array(world, added); + flecs_update_component_monitor_w_array(world, removed); +} + +static +void flecs_commit( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *dst_table, + ecs_table_diff_t *diff, + bool construct, + ecs_flags32_t evt_flags) +{ + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + flecs_journal_begin(world, EcsJournalMove, entity, + &diff->added, &diff->removed); + + ecs_table_t *src_table = NULL; + int is_trav = 0; + if (record) { + src_table = record->table; + is_trav = (record->row & EcsEntityIsTraversable) != 0; + } + + 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 union relationship could have changed. */ + if (src_table) { + flecs_notify_on_add(world, src_table, src_table, + ECS_RECORD_TO_ROW(record->row), 1, &diff->added, evt_flags); + } + flecs_journal_end(); + return; + } + + if (src_table) { + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_table_traversable_add(dst_table, is_trav); + + if (dst_table->type.count) { + flecs_move_entity(world, entity, record, dst_table, diff, + construct, evt_flags); + } else { + flecs_delete_entity(world, record, diff); + record->table = NULL; + } + + flecs_table_traversable_add(src_table, -is_trav); + } else { + flecs_table_traversable_add(dst_table, is_trav); + if (dst_table->type.count) { + flecs_new_entity(world, entity, record, dst_table, diff, + construct, evt_flags); + } + } + + /* 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 (is_trav) { + flecs_update_component_monitors(world, &diff->added, &diff->removed); + } + + if ((!src_table || !src_table->type.count) && world->range_check_enabled) { + ecs_check(!world->info.max_id || entity <= world->info.max_id, + ECS_OUT_OF_RANGE, 0); + ecs_check(entity >= world->info.min_id, + ECS_OUT_OF_RANGE, 0); + } + +error: + flecs_journal_end(); + return; +} + +static +const ecs_entity_t* flecs_bulk_new( + ecs_world_t *world, + ecs_table_t *table, + const ecs_entity_t *entities, + ecs_type_t *component_ids, + int32_t count, + void **component_data, + bool is_move, + int32_t *row_out, + ecs_table_diff_t *diff) +{ + int32_t sparse_count = 0; + if (!entities) { + sparse_count = flecs_entities_count(world); + entities = flecs_entities_new_ids(world, count); + } + + if (!table) { + return entities; + } + + ecs_type_t type = table->type; + if (!type.count) { + return entities; + } + + ecs_type_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = type.array; + component_array.count = type.count; + } + + ecs_data_t *data = &table->data; + int32_t row = flecs_table_appendn(world, table, data, count, entities); + + /* Update entity index. */ + int i; + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + r->table = table; + r->row = ECS_ROW_TO_RECORD(row + i, 0); + } + + flecs_defer_begin(world, &world->stages[0]); + flecs_notify_on_add(world, table, NULL, row, count, &diff->added, + (component_data == NULL) ? 0 : EcsEventNoOnSet); + + if (component_data) { + int32_t c_i; + for (c_i = 0; c_i < component_ids->count; c_i ++) { + void *src_ptr = component_data[c_i]; + if (!src_ptr) { + continue; + } + + /* Find component in storage type */ + ecs_entity_t id = component_ids->array[c_i]; + const ecs_table_record_t *tr = flecs_table_record_get( + world, table, id); + ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, + "id is not a component"); + ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, + "id is not a component"); + ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, + "ids cannot be wildcards"); + + int32_t index = tr->column; + ecs_column_t *column = &table->data.columns[index]; + ecs_type_info_t *ti = column->ti; + int32_t size = column->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *ptr = ecs_vec_get(&column->data, size, row); + + ecs_copy_t copy; + ecs_move_t move; + if (is_move && (move = ti->hooks.move)) { + move(ptr, src_ptr, count, ti); + } else if (!is_move && (copy = ti->hooks.copy)) { + copy(ptr, src_ptr, count, ti); + } else { + ecs_os_memcpy(ptr, src_ptr, size * count); + } + }; + + int32_t j, storage_count = table->column_count; + for (j = 0; j < storage_count; j ++) { + ecs_type_t set_type = { + .array = &table->data.columns[j].id, + .count = 1 + }; + flecs_notify_on_set(world, table, row, count, &set_type, true); + } + } + + flecs_defer_end(world, &world->stages[0]); + + if (row_out) { + *row_out = row; + } + + if (sparse_count) { + entities = flecs_entities_ids(world); + return &entities[sparse_count]; + } else { + return entities; + } +} + +static +void flecs_add_id_w_record( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_id_t id, + bool construct) +{ + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = record->table; + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, &id, &diff); + flecs_commit(world, entity, record, dst_table, &diff, construct, + EcsEventNoOnSet); /* No OnSet, this function is only called from + * functions that are about to set the component. */ +} + +static +void flecs_add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_add(stage, entity, id)) { + return; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_table_t *src_table = r->table; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, &id, &diff); + + flecs_commit(world, entity, r, dst_table, &diff, true, 0); + + flecs_defer_end(world, stage); +} + +static +void flecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_remove(stage, entity, id)) { + return; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *src_table = r->table; + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, &id, &diff); + + flecs_commit(world, entity, r, dst_table, &diff, true, 0); + + flecs_defer_end(world, stage); +} + +static +flecs_component_ptr_t flecs_get_mut( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t id, + ecs_record_t *r) +{ + flecs_component_ptr_t dst = {0}; + + ecs_poly_assert(world, ecs_world_t); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check((id & ECS_COMPONENT_MASK) == id || + ECS_HAS_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, NULL); + + if (r->table) { + dst = flecs_get_component_ptr( + world, r->table, ECS_RECORD_TO_ROW(r->row), id); + if (dst.ptr) { + return dst; + } + } + + /* If entity didn't have component yet, add it */ + flecs_add_id_w_record(world, entity, r, id, true); + + /* Flush commands so the pointer we're fetching is stable */ + flecs_defer_end(world, &world->stages[0]); + flecs_defer_begin(world, &world->stages[0]); + + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table->column_count != 0, ECS_INTERNAL_ERROR, NULL); + dst = flecs_get_component_ptr( + world, r->table, ECS_RECORD_TO_ROW(r->row), id); +error: + return dst; +} + +void flecs_invoke_hook( + ecs_world_t *world, + ecs_table_t *table, + int32_t count, + int32_t row, + ecs_entity_t *entities, + void *ptr, + ecs_id_t id, + const ecs_type_info_t *ti, + ecs_entity_t event, + ecs_iter_action_t hook) +{ + int32_t defer = world->stages[0].defer; + if (defer < 0) { + world->stages[0].defer *= -1; + } + + ecs_iter_t it = { .field_count = 1}; + it.entities = entities; + + flecs_iter_init(world, &it, flecs_iter_cache_all); + it.world = world; + it.real_world = world; + it.table = table; + it.ptrs[0] = ptr; + it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->size); + it.ids[0] = id; + it.event = event; + it.event_id = id; + it.ctx = ti->hooks.ctx; + it.binding_ctx = ti->hooks.binding_ctx; + it.count = count; + it.offset = row; + flecs_iter_validate(&it); + hook(&it); + ecs_iter_fini(&it); + + world->stages[0].defer = defer; +} + +void flecs_notify_on_set( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *ids, + bool owned) +{ + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_data_t *data = &table->data; + + ecs_entity_t *entities = ecs_vec_get_t( + &data->entities, ecs_entity_t, row); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vec_count(&data->entities), + ECS_INTERNAL_ERROR, NULL); + + if (owned) { + int i; + for (i = 0; i < ids->count; i ++) { + ecs_id_t id = ids->array[i]; + const ecs_table_record_t *tr = flecs_table_record_get(world, + table, id); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + + int32_t index = tr->column; + ecs_column_t *column = &table->data.columns[index]; + const ecs_type_info_t *ti = column->ti; + ecs_iter_action_t on_set = ti->hooks.on_set; + if (on_set) { + ecs_vec_t *c = &column->data; + void *ptr = ecs_vec_get(c, column->size, row); + flecs_invoke_hook(world, table, count, row, entities, ptr, id, + ti, EcsOnSet, on_set); + } + } + } + + /* Run OnSet notifications */ + if (table->flags & EcsTableHasOnSet && ids->count) { + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnSet, + .ids = ids, + .table = table, + .offset = row, + .count = count, + .observable = world + }); + } +} + +void flecs_record_add_flag( + ecs_record_t *record, + uint32_t flag) +{ + if (flag == EcsEntityIsTraversable) { + if (!(record->row & flag)) { + ecs_table_t *table = record->table; + if (table) { + flecs_table_traversable_add(table, 1); + } + } + } + record->row |= flag; +} + +void flecs_add_flag( + ecs_world_t *world, + ecs_entity_t entity, + uint32_t flag) +{ + ecs_record_t *record = flecs_entities_get_any(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(record, flag); +} + +/* -- Public functions -- */ + +bool ecs_commit( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + const ecs_type_t *added, + const ecs_type_t *removed) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + + ecs_table_t *src_table = NULL; + if (!record) { + record = flecs_entities_get(world, entity); + src_table = record->table; + } + + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + + if (added) { + diff.added = *added; + } + if (removed) { + diff.removed = *removed; + } + + ecs_defer_begin(world); + flecs_commit(world, entity, record, table, &diff, true, 0); + ecs_defer_end(world); + + return src_table != table; +error: + return false; +} + +ecs_entity_t ecs_set_with( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_id_t prev = stage->with; + stage->with = id; + return prev; +error: + return 0; +} + +ecs_id_t ecs_get_with( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->with; +error: + return 0; +} + +ecs_entity_t ecs_new_id( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + + /* It is possible that the world passed to this function is a stage, so + * make sure we have the actual world. Cast away const since this is one of + * the few functions that may modify the world while it is in readonly mode, + * since it is thread safe (uses atomic inc when in threading mode) */ + ecs_world_t *unsafe_world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); + + ecs_entity_t entity; + if (stage->async || (unsafe_world->flags & EcsWorldMultiThreaded)) { + /* When using an async stage or world is in multithreading mode, make + * sure OS API has threading functions initialized */ + ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, NULL); + + /* Can't atomically increase number above max int */ + ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, + ECS_INVALID_OPERATION, NULL); + entity = (ecs_entity_t)ecs_os_ainc( + (int32_t*)&flecs_entities_max_id(unsafe_world)); + } else { + entity = flecs_entities_new_id(unsafe_world); + } + + ecs_assert(!unsafe_world->info.max_id || + ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, + ECS_OUT_OF_RANGE, NULL); + + flecs_journal(world, EcsJournalNew, entity, 0, 0); + + return entity; +error: + return 0; +} + +ecs_entity_t ecs_new_low_id( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + /* It is possible that the world passed to this function is a stage, so + * make sure we have the actual world. Cast away const since this is one of + * the few functions that may modify the world while it is in readonly mode, + * but only if single threaded. */ + ecs_world_t *unsafe_world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); + if (unsafe_world->flags & EcsWorldReadonly) { + /* Can't issue new comp id while iterating when in multithreaded mode */ + ecs_check(ecs_get_stage_count(world) <= 1, + ECS_INVALID_WHILE_READONLY, NULL); + } + + ecs_entity_t id = 0; + if (unsafe_world->info.last_component_id < FLECS_HI_COMPONENT_ID) { + do { + id = unsafe_world->info.last_component_id ++; + } while (ecs_exists(unsafe_world, id) && id <= FLECS_HI_COMPONENT_ID); + } + + if (!id || id >= FLECS_HI_COMPONENT_ID) { + /* If the low component ids are depleted, return a regular entity id */ + id = ecs_new_id(unsafe_world); + } else { + flecs_entities_ensure(world, id); + } + + ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); + + return id; +error: + return 0; +} + +ecs_entity_t ecs_new_w_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!id || ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); + + ecs_id_t ids[3]; + ecs_type_t to_add = { .array = ids, .count = 0 }; + + if (id) { + ids[to_add.count ++] = id; + } + + ecs_id_t with = stage->with; + if (with) { + ids[to_add.count ++] = with; + } + + ecs_entity_t scope = stage->scope; + if (scope) { + if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { + ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); + } + } + if (to_add.count) { + if (flecs_defer_add(stage, entity, to_add.array[0])) { + int i; + for (i = 1; i < to_add.count; i ++) { + flecs_defer_add(stage, entity, to_add.array[i]); + } + return entity; + } + + int32_t i, count = to_add.count; + ecs_table_t *table = &world->store.root; + + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + for (i = 0; i < count; i ++) { + table = flecs_find_table_add( + world, table, to_add.array[i], &diff); + } + + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_new_entity(world, entity, r, table, &table_diff, true, true); + flecs_table_diff_builder_fini(world, &diff); + } else { + if (flecs_defer_cmd(stage)) { + return entity; + } + + flecs_entities_ensure(world, entity); + } + flecs_defer_end(world, stage); + + return entity; +error: + return 0; +} + +ecs_entity_t ecs_new_w_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); + ecs_record_t *r = flecs_entities_get(world, entity); + + ecs_table_diff_t table_diff = { .added = table->type }; + flecs_new_entity(world, entity, r, table, &table_diff, true, true); + return entity; +error: + return 0; +} + +#ifdef FLECS_PARSER + +/* Traverse table graph by either adding or removing identifiers parsed from the + * passed in expression. */ +static +ecs_table_t *flecs_traverse_from_expr( + ecs_world_t *world, + ecs_table_t *table, + const char *name, + const char *expr, + ecs_table_diff_builder_t *diff, + bool replace_and, + bool *error) +{ + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term( + world, name, expr, ptr, &term, NULL, NULL, false))) + { + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (!(term.first.flags & (EcsSelf|EcsUp))) { + term.first.flags = EcsSelf; + } + if (!(term.second.flags & (EcsSelf|EcsUp))) { + term.second.flags = EcsSelf; + } + if (!(term.src.flags & (EcsSelf|EcsUp))) { + term.src.flags = EcsSelf; + } + + if (ecs_term_finalize(world, &term)) { + ecs_term_fini(&term); + if (error) { + *error = true; + } + return NULL; + } + + if (!ecs_id_is_valid(world, term.id)) { + ecs_term_fini(&term); + ecs_parser_error(name, expr, (ptr - expr), + "invalid term for add expression"); + return NULL; + } + + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + table = flecs_find_table_add(world, table, term.id, diff); + } + + ecs_term_fini(&term); + } + + if (!ptr) { + if (error) { + *error = true; + } + return NULL; + } + } + + return table; +} + +/* Add/remove components based on the parsed expression. This operation is + * slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ +static +void flecs_defer_from_expr( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const char *expr, + bool is_add, + bool replace_and) +{ + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term, + NULL, NULL, false))) + { + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (ecs_term_finalize(world, &term)) { + return; + } + + if (!ecs_id_is_valid(world, term.id)) { + ecs_term_fini(&term); + ecs_parser_error(name, expr, (ptr - expr), + "invalid term for add expression"); + return; + } + + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + if (is_add) { + ecs_add_id(world, entity, term.id); + } else { + ecs_remove_id(world, entity, term.id); + } + } + + ecs_term_fini(&term); + } + } +} +#endif + +/* If operation is not deferred, add components by finding the target + * table and moving the entity towards it. */ +static +int flecs_traverse_add( + ecs_world_t *world, + ecs_entity_t result, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool flecs_new_entity, + bool name_assigned) +{ + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + /* Find existing table */ + ecs_table_t *src_table = NULL, *table = NULL; + ecs_record_t *r = flecs_entities_get(world, result); + table = r->table; + + /* If a name is provided but not yet assigned, add the Name component */ + if (name && !name_assigned) { + table = flecs_find_table_add(world, table, + ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); + } + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { + bool should_add = true; + if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { + scope = ECS_PAIR_SECOND(id); + if ((!desc->id && desc->name) || (name && !name_assigned)) { + /* If name is added to entity, pass scope to add_path instead + * of adding it to the table. The provided name may have nested + * elements, in which case the parent provided here is not the + * parent the entity will end up with. */ + should_add = false; + } + } + if (should_add) { + table = flecs_find_table_add(world, table, id, &diff); + } + } + + /* Find destination table */ + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (flecs_new_entity) { + if (flecs_new_entity && scope && !name && !name_assigned) { + table = flecs_find_table_add( + world, table, ecs_pair(EcsChildOf, scope), &diff); + } + if (with) { + table = flecs_find_table_add(world, table, with, &diff); + } + } + + /* Add components from the 'add_expr' expression */ + if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { +#ifdef FLECS_PARSER + bool error = false; + table = flecs_traverse_from_expr( + world, table, name, desc->add_expr, &diff, true, &error); + if (error) { + flecs_table_diff_builder_fini(world, &diff); + return -1; + } +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Commit entity to destination table */ + if (src_table != table) { + flecs_defer_begin(world, &world->stages[0]); + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_commit(world, result, r, table, &table_diff, true, 0); + flecs_table_diff_builder_fini(world, &diff); + flecs_defer_end(world, &world->stages[0]); + } + + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); + ecs_assert(ecs_get_name(world, result) != NULL, + ECS_INTERNAL_ERROR, NULL); + } + + if (desc->symbol && desc->symbol[0]) { + const char *sym = ecs_get_symbol(world, result); + if (sym) { + ecs_assert(!ecs_os_strcmp(desc->symbol, sym), + ECS_INCONSISTENT_NAME, desc->symbol); + } else { + ecs_set_symbol(world, result, desc->symbol); + } + } + + flecs_table_diff_builder_fini(world, &diff); + return 0; +} + +/* When in deferred mode, we need to add/remove components one by one using + * the regular operations. */ +static +void flecs_deferred_add_remove( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool flecs_new_entity, + bool name_assigned) +{ + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; + + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (flecs_new_entity) { + if (flecs_new_entity && scope && !name && !name_assigned) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } + + if (with) { + ecs_add_id(world, entity, with); + } + } + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { + bool defer = true; + if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { + scope = ECS_PAIR_SECOND(id); + if (name && (!desc->id || !name_assigned)) { + /* New named entities are created by temporarily going out of + * readonly mode to ensure no duplicates are created. */ + defer = false; + } + } + if (defer) { + ecs_add_id(world, entity, id); + } + } + + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { +#ifdef FLECS_PARSER + flecs_defer_from_expr(world, entity, name, desc->add_expr, true, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + int32_t thread_count = ecs_get_stage_count(world); + + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); + } + + /* Set symbol */ + if (desc->symbol) { + const char *sym = ecs_get_symbol(world, entity); + if (!sym || ecs_os_strcmp(sym, desc->symbol)) { + if (thread_count <= 1) { /* See above */ + ecs_suspend_readonly_state_t state; + ecs_world_t *real_world = flecs_suspend_readonly(world, &state); + ecs_set_symbol(world, entity, desc->symbol); + flecs_resume_readonly(real_world, &state); + } else { + ecs_set_symbol(world, entity, desc->symbol); + } + } + } +} + +ecs_entity_t ecs_entity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t scope = stage->scope; + ecs_id_t with = ecs_get_with(world); + ecs_entity_t result = desc->id; + + const char *name = desc->name; + const char *sep = desc->sep; + if (!sep) { + sep = "."; + } + + if (name) { + if (!name[0]) { + name = NULL; + } else if (flecs_name_is_id(name)){ + ecs_entity_t id = flecs_name_to_id(world, name); + if (!id) { + return 0; + } + if (result && (id != result)) { + ecs_err("name id conflicts with provided id"); + return 0; + } + name = NULL; + result = id; + } + } + + const char *root_sep = desc->root_sep; + bool flecs_new_entity = false; + bool name_assigned = false; + + /* Remove optional prefix from name. Entity names can be derived from + * language identifiers, such as components (typenames) and systems + * function names). Because C does not have namespaces, such identifiers + * often encode the namespace as a prefix. + * To ensure interoperability between C and C++ (and potentially other + * languages with namespacing) the entity must be stored without this prefix + * and with the proper namespace, which is what the name_prefix is for */ + const char *prefix = world->info.name_prefix; + if (name && prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(name, prefix, len) && + (isupper(name[len]) || name[len] == '_')) + { + if (name[len] == '_') { + name = name + len + 1; + } else { + name = name + len; + } + } + } + + /* Find or create entity */ + if (!result) { + if (name) { + /* If add array contains a ChildOf pair, use it as scope instead */ + const ecs_id_t *ids = desc->add; + ecs_id_t id; + int32_t i = 0; + while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { + if (ECS_HAS_ID_FLAG(id, PAIR) && + (ECS_PAIR_FIRST(id) == EcsChildOf)) + { + scope = ECS_PAIR_SECOND(id); + } + } + + result = ecs_lookup_path_w_sep( + world, scope, name, sep, root_sep, false); + if (result) { + name_assigned = true; + } + } + + if (!result) { + if (desc->use_low_id) { + result = ecs_new_low_id(world); + } else { + result = ecs_new_id(world); + } + flecs_new_entity = true; + ecs_assert(ecs_get_type(world, result) == NULL, + ECS_INTERNAL_ERROR, NULL); + } + } else { + /* Make sure provided id is either alive or revivable */ + ecs_ensure(world, result); + + name_assigned = ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName); + if (name && name_assigned) { + /* If entity has name, verify that name matches. The name provided + * to the function could either have been relative to the current + * scope, or fully qualified. */ + char *path; + ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; + if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { + /* Fully qualified name was provided, so make sure to + * compare with fully qualified name */ + path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); + } else { + /* Relative name was provided, so make sure to compare with + * relative name */ + if (!sep || sep[0]) { + path = ecs_get_path_w_sep(world, scope, result, sep, ""); + } else { + /* Safe, only freed when sep is valid */ + path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); + } + } + if (path) { + if (ecs_os_strcmp(path, name)) { + /* Mismatching name */ + ecs_err("existing entity '%s' is initialized with " + "conflicting name '%s'", path, name); + if (!sep || sep[0]) { + ecs_os_free(path); + } + return 0; + } + if (!sep || sep[0]) { + ecs_os_free(path); + } + } + } + } + + ecs_assert(name_assigned == ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName), + ECS_INTERNAL_ERROR, NULL); + + if (stage->defer) { + flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, + scope, with, flecs_new_entity, name_assigned); + } else { + if (flecs_traverse_add(world, result, name, desc, + scope, with, flecs_new_entity, name_assigned)) + { + return 0; + } + } + + return result; +error: + return 0; +} + +const ecs_entity_t* ecs_bulk_init( + ecs_world_t *world, + const ecs_bulk_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + + const ecs_entity_t *entities = desc->entities; + int32_t count = desc->count; + + int32_t sparse_count = 0; + if (!entities) { + sparse_count = flecs_entities_count(world); + entities = flecs_entities_new_ids(world, count); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + int i; + for (i = 0; i < count; i ++) { + ecs_ensure(world, entities[i]); + } + } + + ecs_type_t ids; + ecs_table_t *table = desc->table; + if (!table) { + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + int32_t i = 0; + ecs_id_t id; + while ((id = desc->ids[i])) { + table = flecs_find_table_add(world, table, id, &diff); + i ++; + } + + ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); + ids.count = i; + + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, + &table_diff); + flecs_table_diff_builder_fini(world, &diff); + } else { + ecs_table_diff_t diff = { + .added.array = table->type.array, + .added.count = table->type.count + }; + ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; + flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, + &diff); + } + + if (!sparse_count) { + return entities; + } else { + /* Refetch entity ids, in case the underlying array was reallocated */ + entities = flecs_entities_ids(world); + return &entities[sparse_count]; + } +error: + return NULL; +} + +static +void flecs_check_component( + ecs_world_t *world, + ecs_entity_t result, + const EcsComponent *ptr, + ecs_size_t size, + ecs_size_t alignment) +{ + if (ptr->size != size) { + char *path = ecs_get_fullpath(world, result); + ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); + ecs_os_free(path); + } + if (ptr->alignment != alignment) { + char *path = ecs_get_fullpath(world, result); + ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); + ecs_os_free(path); + } +} + +ecs_entity_t ecs_component_init( + ecs_world_t *world, + const ecs_component_desc_t *desc) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + + /* If existing entity is provided, check if it is already registered as a + * component and matches the size/alignment. This can prevent having to + * suspend readonly mode, and increases the number of scenarios in which + * this function can be called in multithreaded mode. */ + ecs_entity_t result = desc->entity; + if (result && ecs_is_alive(world, result)) { + const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); + if (const_ptr) { + flecs_check_component(world, result, const_ptr, + desc->type.size, desc->type.alignment); + return result; + } + } + + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); + + bool new_component = true; + if (!result) { + result = ecs_new_low_id(world); + } else { + ecs_ensure(world, result); + new_component = ecs_has(world, result, EcsComponent); + } + + EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); + if (!ptr->size) { + ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); + ptr->size = desc->type.size; + ptr->alignment = desc->type.alignment; + if (!new_component || ptr->size != desc->type.size) { + if (!ptr->size) { + ecs_trace("#[green]tag#[reset] %s created", + ecs_get_name(world, result)); + } else { + ecs_trace("#[green]component#[reset] %s created", + ecs_get_name(world, result)); + } + } + } else { + flecs_check_component(world, result, ptr, + desc->type.size, desc->type.alignment); + } + + ecs_modified(world, result, EcsComponent); + + if (desc->type.size && + !ecs_id_in_use(world, result) && + !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) + { + ecs_set_hooks_id(world, result, &desc->type.hooks); + } + + if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { + world->info.last_component_id = result + 1; + } + + /* Ensure components cannot be deleted */ + ecs_add_pair(world, result, EcsOnDelete, EcsPanic); + + flecs_resume_readonly(world, &readonly_state); + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); + + return result; +error: + return 0; +} + +const ecs_entity_t* ecs_bulk_new_w_id( + ecs_world_t *world, + ecs_id_t id, + int32_t count) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + const ecs_entity_t *ids; + if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { + return ids; + } + + ecs_table_t *table = &world->store.root; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + if (id) { + table = flecs_find_table_add(world, table, id, &diff); + } + + ecs_table_diff_t td; + flecs_table_diff_build_noalloc(&diff, &td); + ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); + flecs_table_diff_builder_fini(world, &diff); + flecs_defer_end(world, stage); + + return ids; +error: + return NULL; +} + +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_clear(stage, entity)) { + return; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = r->table; + if (table) { + ecs_table_diff_t diff = { + .removed = table->type + }; + + flecs_delete_entity(world, r, &diff); + r->table = NULL; + + if (r->row & EcsEntityIsTraversable) { + flecs_table_traversable_add(table, -1); + } + } + + flecs_defer_end(world, stage); +error: + return; +} + +static +void flecs_throw_invalid_delete( + ecs_world_t *world, + ecs_id_t id) +{ + char *id_str = NULL; + if (!(world->flags & EcsWorldQuit)) { + id_str = ecs_id_str(world, id); + ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); + } +error: + ecs_os_free(id_str); +} + +static +void flecs_marked_id_push( + ecs_world_t *world, + ecs_id_record_t* idr, + ecs_entity_t action, + bool delete_id) +{ + ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, + &world->store.marked_ids, ecs_marked_id_t); + + m->idr = idr; + m->id = idr->id; + m->action = action; + m->delete_id = delete_id; + + flecs_id_record_claim(world, idr); +} + +static +void flecs_id_mark_for_delete( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_entity_t action, + bool delete_id); + +static +void flecs_targets_mark_for_delete( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_id_record_t *idr; + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + int32_t i, count = ecs_vec_count(&table->data.entities); + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if (!r) { + continue; + } + + /* If entity is not used as id or as relationship target, there won't + * be any tables with a reference to it. */ + ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; + if (!(flags & (EcsEntityIsId|EcsEntityIsTarget))) { + continue; + } + + ecs_entity_t e = entities[i]; + if (flags & EcsEntityIsId) { + if ((idr = flecs_id_record_get(world, e))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE(idr->flags), true); + } + if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE(idr->flags), true); + } + } + if (flags & EcsEntityIsTarget) { + if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE_TARGET(idr->flags), true); + } + if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { + flecs_id_mark_for_delete(world, idr, + ECS_ID_ON_DELETE_TARGET(idr->flags), true); + } + } + } +} + +static +bool flecs_id_is_delete_target( + ecs_id_t id, + ecs_entity_t action) +{ + if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { + /* If no explicit delete action is provided, and the id we're deleting + * has the form (*, Target), use OnDeleteTarget action */ + return true; + } + return false; +} + +static +ecs_entity_t flecs_get_delete_action( + ecs_table_t *table, + ecs_table_record_t *tr, + ecs_entity_t action, + bool delete_target) +{ + ecs_entity_t result = action; + if (!result && delete_target) { + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_id_t id = idr->id; + + /* If action is not specified and we're deleting a relationship target, + * derive the action from the current record */ + int32_t i = tr->index, count = tr->count; + do { + ecs_type_t *type = &table->type; + ecs_table_record_t *trr = &table->_->records[i]; + ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; + result = ECS_ID_ON_DELETE_TARGET(idrr->flags); + if (result == EcsDelete) { + /* Delete takes precedence over Remove */ + break; + } + + if (count > 1) { + /* If table contains multiple pairs for target they are not + * guaranteed to occupy consecutive elements in the table's type + * vector, so a linear search is needed to find matches. */ + for (++ i; i < type->count; i ++) { + if (ecs_id_match(type->array[i], id)) { + break; + } + } + + /* We should always have as many matching ids as tr->count */ + ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); + } + } while (--count); + } + + return result; +} + +static +void flecs_update_monitors_for_delete( + ecs_world_t *world, + ecs_id_t id) +{ + flecs_update_component_monitors(world, NULL, &(ecs_type_t){ + .array = (ecs_id_t[]){id}, + .count = 1 + }); +} + +static +void flecs_id_mark_for_delete( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_entity_t action, + bool delete_id) +{ + if (idr->flags & EcsIdMarkedForDelete) { + return; + } + + idr->flags |= EcsIdMarkedForDelete; + flecs_marked_id_push(world, idr, action, delete_id); + + ecs_id_t id = idr->id; + + bool delete_target = flecs_id_is_delete_target(id, action); + + /* Mark all tables with the id for delete */ + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (table->flags & EcsTableMarkedForDelete) { + continue; + } + + ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, + delete_target); + + /* If this is a Delete action, recursively mark ids & tables */ + if (cur_action == EcsDelete) { + table->flags |= EcsTableMarkedForDelete; + ecs_log_push_2(); + flecs_targets_mark_for_delete(world, table); + ecs_log_pop_2(); + } else if (cur_action == EcsPanic) { + flecs_throw_invalid_delete(world, id); + } + } + } + + /* Same for empty tables */ + if (flecs_table_cache_empty_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + tr->hdr.table->flags |= EcsTableMarkedForDelete; + } + } + + /* Signal query cache monitors */ + flecs_update_monitors_for_delete(world, id); + + /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ + if (ecs_id_is_wildcard(id)) { + ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *cur = idr; + if (ECS_PAIR_SECOND(id) == EcsWildcard) { + while ((cur = cur->first.next)) { + flecs_update_monitors_for_delete(world, cur->id); + } + } else { + ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + while ((cur = cur->second.next)) { + flecs_update_monitors_for_delete(world, cur->id); + } + } + } +} + +static +bool flecs_on_delete_mark( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t action, + bool delete_id) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + /* If there's no id record, there's nothing to delete */ + return false; + } + + if (!action) { + /* If no explicit action is provided, derive it */ + if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { + /* Delete actions are determined by the component, or in the case + * of a pair by the relationship. */ + action = ECS_ID_ON_DELETE(idr->flags); + } + } + + if (action == EcsPanic) { + /* This id is protected from deletion */ + flecs_throw_invalid_delete(world, id); + return false; + } + + flecs_id_mark_for_delete(world, idr, action, delete_id); + + return true; +} + +static +void flecs_remove_from_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_table_diff_t temp_diff; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + ecs_table_t *dst_table = table; + + /* To find the dst table, remove all ids that are marked for deletion */ + int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); + ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); + const ecs_table_record_t *tr; + for (i = 0; i < count; i ++) { + const ecs_id_record_t *idr = ids[i].idr; + + if (!(tr = flecs_id_record_get_table(idr, dst_table))) { + continue; + } + + t = tr->index; + + do { + ecs_id_t id = dst_table->type.array[t]; + dst_table = flecs_table_traverse_remove( + world, dst_table, &id, &temp_diff); + flecs_table_diff_build_append_table(world, &diff, &temp_diff); + } while (dst_table->type.count && (t = ecs_search_offset( + world, dst_table, t, idr->id, NULL)) != -1); + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!dst_table->type.count) { + /* If this removes all components, clear table */ + flecs_table_clear_entities(world, table); + } else { + /* Otherwise, merge table into dst_table */ + if (dst_table != table) { + int32_t table_count = ecs_table_count(table); + if (diff.removed.count && table_count) { + ecs_log_push_3(); + ecs_table_diff_t td; + flecs_table_diff_build_noalloc(&diff, &td); + flecs_notify_on_remove(world, table, NULL, 0, table_count, + &td.removed); + ecs_log_pop_3(); + } + + flecs_table_merge(world, dst_table, table, + &dst_table->data, &table->data); + } + } + + flecs_table_diff_builder_fini(world, &diff); +} + +static +bool flecs_on_delete_clear_tables( + ecs_world_t *world) +{ + /* Iterate in reverse order so that DAGs get deleted bottom to top */ + int32_t i, last = ecs_vec_count(&world->store.marked_ids), first = 0; + ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); + do { + for (i = last - 1; i >= first; i --) { + ecs_id_record_t *idr = ids[i].idr; + ecs_entity_t action = ids[i].action; + + /* Empty all tables for id */ + { + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + + if ((action == EcsRemove) || + !(table->flags & EcsTableMarkedForDelete)) + { + flecs_remove_from_table(world, table); + } else { + ecs_dbg_3( + "#[red]delete#[reset] entities from table %u", + (uint32_t)table->id); + flecs_table_delete_entities(world, table); + } + } + } + } + + /* Run commands so children get notified before parent is deleted */ + if (world->stages[0].defer) { + flecs_defer_end(world, &world->stages[0]); + flecs_defer_begin(world, &world->stages[0]); + } + + /* User code (from triggers) could have enqueued more ids to delete, + * reobtain the array in case it got reallocated */ + ids = ecs_vec_first(&world->store.marked_ids); + } + + /* Check if new ids were marked since we started */ + int32_t new_last = ecs_vec_count(&world->store.marked_ids); + if (new_last != last) { + /* Iterate remaining ids */ + ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); + first = last; + last = new_last; + } else { + break; + } + } while (true); + + return true; +} + +static +bool flecs_on_delete_clear_ids( + ecs_world_t *world) +{ + int32_t i, count = ecs_vec_count(&world->store.marked_ids); + ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); + int twice = 2; + + do { + for (i = 0; i < count; i ++) { + /* Release normal ids before wildcard ids */ + if (ecs_id_is_wildcard(ids[i].id)) { + if (twice == 2) { + continue; + } + } else { + if (twice == 1) { + continue; + } + } + + ecs_id_record_t *idr = ids[i].idr; + bool delete_id = ids[i].delete_id; + + flecs_id_record_release_tables(world, idr); + + /* Release the claim taken by flecs_marked_id_push. This may delete the + * id record as all other claims may have been released. */ + int32_t rc = flecs_id_record_release(world, idr); + ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); + (void)rc; + + /* If rc is 0, the id was likely deleted by a nested delete_with call + * made by an on_remove handler/OnRemove observer */ + if (rc) { + if (delete_id) { + /* If id should be deleted, release initial claim. This happens when + * a component, tag, or part of a pair is deleted. */ + flecs_id_record_release(world, idr); + } else { + /* If id should not be deleted, unmark id record for deletion. This + * happens when all instances *of* an id are deleted, for example + * when calling ecs_remove_all or ecs_delete_with. */ + idr->flags &= ~EcsIdMarkedForDelete; + } + } + } + } while (-- twice); + + return true; +} + +static +void flecs_on_delete( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t action, + bool delete_id) +{ + /* Cleanup can happen recursively. If a cleanup action is already in + * progress, only append ids to the marked_ids. The topmost cleanup + * frame will handle the actual cleanup. */ + int32_t count = ecs_vec_count(&world->store.marked_ids); + + /* Make sure we're evaluating a consistent list of non-empty tables */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* Collect all ids that need to be deleted */ + flecs_on_delete_mark(world, id, action, delete_id); + + /* Only perform cleanup if we're the first stack frame doing it */ + if (!count && ecs_vec_count(&world->store.marked_ids)) { + ecs_dbg_2("#[red]delete#[reset]"); + ecs_log_push_2(); + + /* Empty tables with all the to be deleted ids */ + flecs_on_delete_clear_tables(world); + + /* All marked tables are empty, ensure they're in the right list */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* Release remaining references to the ids */ + flecs_on_delete_clear_ids(world); + + /* Verify deleted ids are no longer in use */ +#ifdef FLECS_DEBUG + ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); + int32_t i; count = ecs_vec_count(&world->store.marked_ids); + for (i = 0; i < count; i ++) { + ecs_assert(!ecs_id_in_use(world, ids[i].id), + ECS_INTERNAL_ERROR, NULL); + } +#endif + ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); + + /* Ids are deleted, clear stack */ + ecs_vec_clear(&world->store.marked_ids); + + ecs_log_pop_2(); + } +} + +void ecs_delete_with( + ecs_world_t *world, + ecs_id_t id) +{ + flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { + return; + } + + flecs_on_delete(world, id, EcsDelete, false); + flecs_defer_end(world, stage); + + flecs_journal_end(); +} + +void ecs_remove_all( + ecs_world_t *world, + ecs_id_t id) +{ + flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { + return; + } + + flecs_on_delete(world, id, EcsRemove, false); + flecs_defer_end(world, stage); + + flecs_journal_end(); +} + +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_delete(stage, entity)) { + return; + } + + ecs_record_t *r = flecs_entities_try(world, entity); + if (r) { + flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); + + ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + ecs_table_t *table; + if (row_flags) { + if (row_flags & EcsEntityIsTarget) { + flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); + flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); + r->idr = NULL; + } + if (row_flags & EcsEntityIsId) { + flecs_on_delete(world, entity, 0, true); + flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); + } + if (row_flags & EcsEntityIsTraversable) { + table = r->table; + if (table) { + flecs_table_traversable_add(table, -1); + } + } + /* Merge operations before deleting entity */ + flecs_defer_end(world, stage); + flecs_defer_begin(world, stage); + } + + table = r->table; + + if (table) { + ecs_table_diff_t diff = { + .removed = table->type + }; + + flecs_delete_entity(world, r, &diff); + + r->row = 0; + r->table = NULL; + } + + flecs_entities_remove(world, entity); + + flecs_journal_end(); + } + + flecs_defer_end(world, stage); +error: + return; +} + +void ecs_add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + flecs_add_id(world, entity, id); +error: + return; +} + +void ecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), + ECS_INVALID_PARAMETER, NULL); + flecs_remove_id(world, entity, id); +error: + return; +} + +void ecs_override_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_add_id(world, entity, ECS_OVERRIDE | id); +error: + return; +} + +ecs_entity_t ecs_clone( + ecs_world_t *world, + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); + ecs_check(!dst || !ecs_get_table(world, dst), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (!dst) { + dst = ecs_new_id(world); + } + + if (flecs_defer_clone(stage, dst, src, copy_value)) { + return dst; + } + + ecs_record_t *src_r = flecs_entities_get(world, src); + ecs_assert(src_r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *src_table = src_r->table; + if (!src_table) { + goto done; + } + + ecs_table_t *dst_table = src_table; + if (src_table->flags & EcsTableHasName) { + dst_table = ecs_table_remove_id(world, src_table, + ecs_pair_t(EcsIdentifier, EcsName)); + } + + ecs_type_t dst_type = dst_table->type; + ecs_table_diff_t diff = { .added = dst_type }; + ecs_record_t *dst_r = flecs_entities_get(world, dst); + flecs_new_entity(world, dst, dst_r, dst_table, &diff, true, true); + int32_t row = ECS_RECORD_TO_ROW(dst_r->row); + + if (copy_value) { + flecs_table_move(world, dst, src, dst_table, + row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); + int32_t i, count = dst_table->column_count; + for (i = 0; i < count; i ++) { + ecs_type_t type = { + .array = &dst_table->data.columns[i].id, + .count = 1 + }; + flecs_notify_on_set(world, dst_table, row, 1, &type, true); + } + } + +done: + flecs_defer_end(world, stage); + return dst; +error: + return 0; +} + +const void* ecs_get_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(flecs_stage_from_readonly_world(world)->async == false, + ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = r->table; + if (!table) { + return NULL; + } + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; + } + + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return flecs_get_base_component(world, table, id, idr, 0); + } else { + ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); + } + + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_get_component_w_index(table, tr->column, row).ptr; +error: + return NULL; +} + +void* ecs_get_mut_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_cmd(stage)) { + return flecs_defer_set(world, stage, EcsCmdMut, entity, id, 0, NULL); + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + void *result = flecs_get_mut(world, entity, id, r).ptr; + ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_defer_end(world, stage); + return result; +error: + return NULL; +} + +void* ecs_get_mut_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); + + return flecs_defer_set(world, stage, EcsCmdSet, entity, id, 0, NULL); +error: + return NULL; +} + +static +ecs_record_t* flecs_access_begin( + ecs_world_t *stage, + ecs_entity_t entity, + bool write) +{ + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table; + if (!(table = r->table)) { + return NULL; + } + + int32_t count = ecs_os_ainc(&table->_->lock); + (void)count; + if (write) { + ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); + } + + return r; +error: + return NULL; +} + +static +void flecs_access_end( + const ecs_record_t *r, + bool write) +{ + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t count = ecs_os_adec(&r->table->_->lock); + (void)count; + if (write) { + ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); + } + ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); + +error: + return; +} + +ecs_record_t* ecs_write_begin( + ecs_world_t *world, + ecs_entity_t entity) +{ + return flecs_access_begin(world, entity, true); +} + +void ecs_write_end( + ecs_record_t *r) +{ + flecs_access_end(r, true); +} + +const ecs_record_t* ecs_read_begin( + ecs_world_t *world, + ecs_entity_t entity) +{ + return flecs_access_begin(world, entity, false); +} + +void ecs_read_end( + const ecs_record_t *r) +{ + flecs_access_end(r, false); +} + +ecs_entity_t ecs_record_get_entity( + const ecs_record_t *record) +{ + ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_table_t *table = record->table; + if (!table) { + return 0; + } + + return ecs_vec_get_t(&table->data.entities, ecs_entity_t, + ECS_RECORD_TO_ROW(record->row))[0]; +error: + return 0; +} + +const void* ecs_record_get_id( + const ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); +} + +bool ecs_record_has_id( + ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + if (r->table) { + return ecs_table_has_id(world, r->table, id); + } + return false; +} + +void* ecs_record_get_mut_id( + ecs_world_t *stage, + ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); +} + +ecs_ref_t ecs_ref_init_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_check(record != NULL, ECS_INVALID_PARAMETER, + "cannot create ref for empty entity"); + + ecs_ref_t result = { + .entity = entity, + .id = id, + .record = record + }; + + ecs_table_t *table = record->table; + if (table) { + result.tr = flecs_table_record_get(world, table, id); + result.table_id = table->id; + } + + return result; +error: + return (ecs_ref_t){0}; +} + +static +bool flecs_ref_needs_sync( + ecs_ref_t *ref, + ecs_table_record_t *tr, + const ecs_table_t *table) +{ + ecs_assert(ref != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return !tr || tr->hdr.table != table || ref->table_id != table->id; +} + +void ecs_ref_update( + const ecs_world_t *world, + ecs_ref_t *ref) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { + return; + } + + ecs_table_record_t *tr = ref->tr; + if (flecs_ref_needs_sync(ref, tr, table)) { + tr = ref->tr = flecs_table_record_get(world, table, ref->id); + if (!tr) { + return; + } + + ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); + ref->table_id = table->id; + } +error: + return; +} + +void* ecs_ref_get_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id == ref->id, ECS_INVALID_PARAMETER, NULL); + + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { + return NULL; + } + + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + + ecs_table_record_t *tr = ref->tr; + if (flecs_ref_needs_sync(ref, tr, table)) { + tr = ref->tr = flecs_table_record_get(world, table, id); + if (!tr) { + return NULL; + } + + ref->table_id = table->id; + ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); + } + + int32_t column = tr->column; + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + return flecs_get_component_w_index(table, column, row).ptr; +error: + return NULL; +} + +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, + "cannot emplace a component the entity already has"); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_cmd(stage)) { + return flecs_defer_set(world, stage, EcsCmdEmplace, entity, id, 0, NULL); + } + + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); + flecs_defer_end(world, stage); + + void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + return ptr; +error: + return NULL; +} + +static +void flecs_modified_id_if( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool owned) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_modified(stage, entity, id)) { + return; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + if (!flecs_table_record_get(world, table, id)) { + flecs_defer_end(world, stage); + return; + } + + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, owned); + + flecs_table_mark_dirty(world, table, id); + flecs_defer_end(world, stage); +error: + return; +} + +void ecs_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_modified(stage, entity, id)) { + 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_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); + + flecs_table_mark_dirty(world, table, id); + flecs_defer_end(world, stage); +error: + return; +} + +static +void flecs_copy_ptr_w_id( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void *ptr) +{ + if (flecs_defer_cmd(stage)) { + flecs_defer_set(world, stage, EcsCmdSet, entity, id, + flecs_utosize(size), ptr); + return; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); + const ecs_type_info_t *ti = dst.ti; + ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ptr) { + ecs_copy_t copy = ti->hooks.copy; + if (copy) { + copy(dst.ptr, ptr, 1, ti); + } else { + ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); + } + } else { + ecs_os_memset(dst.ptr, 0, size); + } + + flecs_table_mark_dirty(world, r->table, id); + + ecs_table_t *table = r->table; + if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set( + world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); + } + + flecs_defer_end(world, stage); +error: + return; +} + +static +void flecs_move_ptr_w_id( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void *ptr, + ecs_cmd_kind_t cmd_kind) +{ + if (flecs_defer_cmd(stage)) { + flecs_defer_set(world, stage, cmd_kind, entity, id, + flecs_utosize(size), ptr); + return; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); + ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_type_info_t *ti = dst.ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_move_t move; + if (cmd_kind != EcsCmdEmplace) { + /* ctor will have happened by get_mut */ + move = ti->hooks.move_dtor; + } else { + move = ti->hooks.ctor_move_dtor; + } + if (move) { + move(dst.ptr, ptr, 1, ti); + } else { + ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); + } + + flecs_table_mark_dirty(world, r->table, id); + + if (cmd_kind == EcsCmdSet) { + ecs_table_t *table = r->table; + if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set( + world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); + } + } + + flecs_defer_end(world, stage); +error: + return; +} + +ecs_entity_t ecs_set_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!entity || ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (!entity) { + entity = ecs_new_id(world); + ecs_entity_t scope = stage->scope; + if (scope) { + ecs_add_pair(world, entity, EcsChildOf, scope); + } + } + + /* Safe to cast away const: function won't modify if move arg is false */ + flecs_copy_ptr_w_id(world, stage, entity, id, size, + ECS_CONST_CAST(void*, ptr)); + return entity; +error: + return 0; +} + +void ecs_enable_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_enable(stage, entity, id, enable)) { + return; + } else { + /* Operations invoked by enable/disable should not be deferred */ + stage->defer --; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_entity_t bs_id = id | ECS_TOGGLE; + + ecs_table_t *table = r->table; + int32_t index = -1; + if (table) { + index = ecs_table_get_type_index(world, table, bs_id); + } + + if (index == -1) { + ecs_add_id(world, entity, bs_id); + ecs_enable_id(world, entity, id, enable); + return; + } + + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + index -= table->_->bs_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_bitset_t *bs = &table->_->bs_columns[index]; + ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); +error: + return; +} + +bool ecs_is_enabled_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + if (!table) { + return false; + } + + ecs_entity_t bs_id = id | ECS_TOGGLE; + int32_t index = ecs_table_get_type_index(world, table, bs_id); + if (index == -1) { + /* If table does not have TOGGLE column for component, component is + * always enabled, if the entity has it */ + return ecs_has_id(world, entity, id); + } + + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + index -= table->_->bs_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &table->_->bs_columns[index]; + + return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); +error: + return false; +} + +bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get_any(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + if (!table) { + return false; + } + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + int32_t column; + if (idr) { + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (tr) { + return true; + } + } + + if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { + return false; + } + + ecs_table_record_t *tr; + column = ecs_search_relation(world, table, 0, id, + EcsIsA, 0, 0, 0, &tr); + if (column == -1) { + return false; + } + + table = tr->hdr.table; + if ((table->flags & EcsTableHasUnion) && ECS_HAS_ID_FLAG(id, PAIR) && + ECS_PAIR_SECOND(id) != EcsWildcard) + { + if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &table->_->sw_columns[ + column - table->_->sw_offset]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + uint64_t value = flecs_switch_get(sw, row); + return value == ecs_pair_second(world, id); + } + } + + return true; +error: + return false; +} + +bool ecs_owns_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1); +} + +ecs_entity_t ecs_get_target( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + if (!table) { + goto not_found; + } + + ecs_id_t wc = ecs_pair(rel, EcsWildcard); + ecs_id_record_t *idr = flecs_id_record_get(world, wc); + const ecs_table_record_t *tr = NULL; + if (idr) { + tr = flecs_id_record_get_table(idr, table); + } + if (!tr) { + if (table->flags & EcsTableHasUnion) { + wc = ecs_pair(EcsUnion, rel); + tr = flecs_table_record_get(world, table, wc); + if (tr) { + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &table->_->sw_columns[ + tr->index - table->_->sw_offset]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_switch_get(sw, row); + + } + } + + if (!idr || !(idr->flags & EcsIdDontInherit)) { + goto look_in_base; + } else { + return 0; + } + } else if (table->flags & EcsTableHasTarget) { + EcsTarget *tf = ecs_table_get_id(world, table, + ecs_pair_t(EcsTarget, rel), ECS_RECORD_TO_ROW(r->row)); + if (tf) { + return ecs_record_get_entity(tf->target); + } + } + + if (index >= tr->count) { + index -= tr->count; + goto look_in_base; + } + + return ecs_pair_second(world, table->type.array[tr->index + index]); +look_in_base: + if (table->flags & EcsTableHasIsA) { + ecs_table_record_t *tr_isa = flecs_id_record_get_table( + world->idr_isa_wildcard, table); + ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t *ids = table->type.array; + int32_t i = tr_isa->index, end = (i + tr_isa->count); + for (; i < end; i ++) { + ecs_id_t isa_pair = ids[i]; + ecs_entity_t base = ecs_pair_second(world, isa_pair); + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t t = ecs_get_target(world, base, rel, index); + if (t) { + return t; + } + } + } + +not_found: +error: + return 0; +} + +ecs_entity_t ecs_get_parent( + const ecs_world_t *world, + ecs_entity_t entity) +{ + return ecs_get_target(world, entity, EcsChildOf, 0); +} + +ecs_entity_t ecs_get_target_for_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + + if (!id) { + return ecs_get_target(world, entity, rel, 0); + } + + world = ecs_get_world(world); + + ecs_table_t *table = ecs_get_table(world, entity); + ecs_entity_t subject = 0; + + if (rel) { + int32_t column = ecs_search_relation( + world, table, 0, id, rel, 0, &subject, 0, 0); + if (column == -1) { + return 0; + } + } else { + entity = 0; /* Don't return entity if id was not found */ + + if (table) { + ecs_id_t *ids = table->type.array; + int32_t i, count = table->type.count; + + for (i = 0; i < count; i ++) { + ecs_id_t ent = ids[i]; + if (ent & ECS_ID_FLAGS_MASK) { + /* Skip ids with pairs, roles since 0 was provided for rel */ + break; + } + + if (ecs_has_id(world, ent, id)) { + subject = ent; + break; + } + } + } + } + + if (subject == 0) { + return entity; + } else { + return subject; + } +error: + return 0; +} + +int32_t ecs_get_depth( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel) +{ + ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return ecs_table_get_depth(world, table, rel); + } + + return 0; +error: + return -1; +} + +static +ecs_entity_t flecs_id_for_depth( + ecs_world_t *world, + int32_t depth) +{ + ecs_vec_t *depth_ids = &world->store.depth_ids; + int32_t i, count = ecs_vec_count(depth_ids); + for (i = count; i <= depth; i ++) { + ecs_entity_t *el = ecs_vec_append_t( + &world->allocator, depth_ids, ecs_entity_t); + el[0] = ecs_new_w_pair(world, EcsChildOf, EcsFlecsInternals); + ecs_map_val_t *v = ecs_map_ensure(&world->store.entity_to_depth, el[0]); + v[0] = flecs_ito(uint64_t, i); + } + + return ecs_vec_get_t(&world->store.depth_ids, ecs_entity_t, depth)[0]; +} + +static +int32_t flecs_depth_for_id( + ecs_world_t *world, + ecs_entity_t id) +{ + ecs_map_val_t *v = ecs_map_get(&world->store.entity_to_depth, id); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_uto(int32_t, v[0]); +} + +static +int32_t flecs_depth_for_flat_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(table->flags & EcsTableHasTarget, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id; + int32_t col = ecs_search(world, table, + ecs_pair(EcsFlatten, EcsWildcard), &id); + ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); + (void)col; + return flecs_depth_for_id(world, ECS_PAIR_SECOND(id)); +} + +static +void flecs_flatten( + ecs_world_t *world, + ecs_entity_t root, + ecs_id_t pair, + int32_t depth, + const ecs_flatten_desc_t *desc) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, pair); + if (!idr) { + return; + } + + ecs_entity_t depth_id = flecs_id_for_depth(world, depth); + ecs_id_t root_pair = ecs_pair(EcsChildOf, root); + ecs_id_t tgt_pair = ecs_pair_t(EcsTarget, EcsChildOf); + ecs_id_t depth_pair = ecs_pair(EcsFlatten, depth_id); + ecs_id_t name_pair = ecs_pair_t(EcsIdentifier, EcsName); + + ecs_entity_t rel = ECS_PAIR_FIRST(pair); + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + int32_t i, count = ecs_table_count(table); + bool has_tgt = table->flags & EcsTableHasTarget; + flecs_emit_propagate_invalidate(world, table, 0, count); + + ecs_entity_t *entities = table->data.entities.array; + for (i = 0; i < count; i ++) { + ecs_record_t *record = flecs_entities_get(world, entities[i]); + ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTarget) { + flecs_flatten(world, root, ecs_pair(rel, entities[i]), + depth + 1, desc); + } + } + + ecs_table_diff_t tmpdiff; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + ecs_table_t *dst; + + dst = flecs_table_traverse_add(world, table, &root_pair, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + + dst = flecs_table_traverse_add(world, dst, &tgt_pair, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + + if (!desc->lose_depth) { + if (!has_tgt) { + dst = flecs_table_traverse_add(world, dst, &depth_pair, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + } else { + int32_t cur_depth = flecs_depth_for_flat_table(world, table); + cur_depth += depth; + ecs_entity_t e_depth = flecs_id_for_depth(world, cur_depth); + ecs_id_t p_depth = ecs_pair(EcsFlatten, e_depth); + dst = flecs_table_traverse_add(world, dst, &p_depth, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + } + } + + if (!desc->keep_names) { + dst = flecs_table_traverse_remove(world, dst, &name_pair, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + } + + int32_t dst_count = ecs_table_count(dst); + + ecs_table_diff_t td; + flecs_table_diff_build_noalloc(&diff, &td); + flecs_notify_on_remove(world, table, NULL, 0, count, &td.removed); + flecs_table_merge(world, dst, table, &dst->data, &table->data); + flecs_notify_on_add(world, dst, NULL, dst_count, count, + &td.added, 0); + flecs_table_diff_builder_fini(world, &diff); + + EcsTarget *fh = ecs_table_get_id(world, dst, tgt_pair, 0); + ecs_assert(fh != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + int32_t remain = count; + + for (i = dst_count; i < (dst_count + count); i ++) { + if (!has_tgt) { + fh[i].target = flecs_entities_get_any(world, + ECS_PAIR_SECOND(pair)); + fh[i].count = remain; + remain --; + } + ecs_assert(fh[i].target != NULL, ECS_INTERNAL_ERROR, NULL); + } + } + } + + ecs_delete_with(world, pair); + flecs_id_record_release(world, idr); +} + +void ecs_flatten( + ecs_world_t *world, + ecs_id_t pair, + const ecs_flatten_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_entity_t rel = ECS_PAIR_FIRST(pair); + ecs_entity_t root = ecs_pair_second(world, pair); + ecs_flatten_desc_t private_desc = {0}; + if (desc) { + private_desc = *desc; + } + + ecs_run_aperiodic(world, 0); + ecs_defer_begin(world); + + ecs_id_record_t *idr = flecs_id_record_get(world, pair); + + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } + + if (table->flags & EcsTableIsPrefab) { + continue; + } + + int32_t i, count = ecs_table_count(table); + ecs_entity_t *entities = table->data.entities.array; + for (i = 0; i < count; i ++) { + ecs_record_t *record = flecs_entities_get(world, entities[i]); + ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTarget) { + flecs_flatten(world, root, ecs_pair(rel, entities[i]), 1, + &private_desc); + } + } + } + } + + ecs_defer_end(world); +} + +static +const char* flecs_get_identifier( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, tag); + + if (ptr) { + return ptr->value; + } else { + return NULL; + } +error: + return NULL; +} + +const char* ecs_get_name( + const ecs_world_t *world, + ecs_entity_t entity) +{ + return flecs_get_identifier(world, entity, EcsName); +} + +const char* ecs_get_symbol( + const ecs_world_t *world, + ecs_entity_t entity) +{ + world = ecs_get_world(world); + if (ecs_owns_pair(world, entity, ecs_id(EcsIdentifier), EcsSymbol)) { + return flecs_get_identifier(world, entity, EcsSymbol); + } else { + return NULL; + } +} + +static +ecs_entity_t flecs_set_identifier( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t tag, + const char *name) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + entity = ecs_new_id(world); + } + + if (!name) { + ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); + return entity; + } + + EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (tag == EcsName) { + /* Insert command after get_mut, but before the name is potentially + * freed. Even though the name is a const char*, it is possible that the + * application passed in the existing name of the entity which could + * still cause it to be freed. */ + flecs_defer_path(stage, 0, entity, name); + } + + ecs_os_strset(&ptr->value, name); + ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); + + return entity; +error: + return 0; +} + +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + if (!entity) { + return ecs_entity(world, { + .name = name + }); + } + + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_set_identifier(world, stage, entity, EcsName, name); + + return entity; +} + +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); +} + +void ecs_set_alias( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + flecs_set_identifier(world, NULL, entity, EcsAlias, name); +} + +ecs_id_t ecs_make_pair( + ecs_entity_t relationship, + ecs_entity_t target) +{ + ecs_assert(!ECS_IS_PAIR(relationship) && !ECS_IS_PAIR(target), + ECS_INVALID_PARAMETER, "cannot create nested pairs"); + return ecs_pair(relationship, target); +} + +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + /* 0 is not a valid entity id */ + if (!entity) { + return false; + } + + /* Entity identifiers should not contain flag bits */ + if (entity & ECS_ID_FLAGS_MASK) { + return false; + } + + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { + return false; + } + + /* If entity doesn't exist in the world, the id is valid as long as the + * generation is 0. Using a non-existing id with a non-zero generation + * requires calling ecs_ensure first. */ + if (!ecs_exists(world, entity)) { + return ECS_GENERATION(entity) == 0; + } + + /* If id exists, it must be alive (the generation count must match) */ + return ecs_is_alive(world, entity); +error: + return false; +} + +ecs_id_t ecs_strip_generation( + ecs_entity_t e) +{ + /* If this is not a pair, erase the generation bits */ + if (!(e & ECS_ID_FLAGS_MASK)) { + e &= ~ECS_GENERATION_MASK; + } + + return e; +} + +bool ecs_is_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + return flecs_entities_is_alive(world, entity); +error: + return false; +} + +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return 0; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + if (flecs_entities_is_alive(world, entity)) { + return entity; + } + + /* Make sure id does not have generation. This guards against accidentally + * "upcasting" a not alive identifier to a alive one. */ + if ((uint32_t)entity != entity) { + return 0; + } + + ecs_entity_t current = flecs_entities_get_generation(world, entity); + if (!current || !flecs_entities_is_alive(world, current)) { + return 0; + } + + return current; +error: + return 0; +} + +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + + /* Const cast is safe, function checks for threading */ + world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); + + /* The entity index can be mutated while in staged/readonly mode, as long as + * the world is not multithreaded. */ + ecs_assert(!(world->flags & EcsWorldMultiThreaded), + ECS_INVALID_OPERATION, NULL); + + /* Check if a version of the provided id is alive */ + ecs_entity_t any = ecs_get_alive(world, (uint32_t)entity); + if (any == entity) { + /* If alive and equal to the argument, there's nothing left to do */ + return; + } + + /* If the id is currently alive but did not match the argument, fail */ + ecs_check(!any, ECS_INVALID_PARAMETER, NULL); + + /* Set generation if not alive. The sparse set checks if the provided + * id matches its own generation which is necessary for alive ids. This + * check would cause ecs_ensure to fail if the generation of the 'entity' + * argument doesn't match with its generation. + * + * While this could've been addressed in the sparse set, this is a rare + * scenario that can only be triggered by ecs_ensure. Implementing it here + * allows the sparse set to not do this check, which is more efficient. */ + flecs_entities_set_generation(world, entity); + + /* Ensure id exists. The underlying data structure will verify that the + * generation count matches the provided one. */ + flecs_entities_ensure(world, entity); +error: + return; +} + +void ecs_ensure_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); + + ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); + + if (flecs_entities_get_generation(world, r) == 0) { + ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, + "first element of pair is not alive"); + flecs_entities_ensure(world, r); + } + if (flecs_entities_get_generation(world, o) == 0) { + ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, + "second element of pair is not alive"); + flecs_entities_ensure(world, o); + } + } else { + flecs_entities_ensure(world, id & ECS_COMPONENT_MASK); + } +error: + return; +} + +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + return flecs_entities_exists(world, entity); +error: + return false; +} + +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + return record->table; +error: + return NULL; +} + +const ecs_type_t* ecs_get_type( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return &table->type; + } + + return NULL; +} + +const ecs_type_info_t* ecs_get_type_info( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr && ECS_IS_PAIR(id)) { + idr = flecs_id_record_get(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); + if (!idr || !idr->type_info) { + idr = NULL; + } + if (!idr) { + ecs_entity_t first = ecs_pair_first(world, id); + if (!first || !ecs_has_id(world, first, EcsTag)) { + idr = flecs_id_record_get(world, + ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); + if (!idr || !idr->type_info) { + idr = NULL; + } + } + } + } + + if (idr) { + return idr->type_info; + } else if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_type_info_get(world, id); + } +error: + return NULL; +} + +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); + return ti->component; + } +error: + return 0; +} + +bool ecs_id_is_tag( + const ecs_world_t *world, + ecs_id_t id) +{ + if (ecs_id_is_wildcard(id)) { + /* If id is a wildcard, we can't tell if it's a tag or not, except + * when the relationship part of a pair has the Tag property */ + if (ECS_HAS_ID_FLAG(id, PAIR)) { + if (ECS_PAIR_FIRST(id) != EcsWildcard) { + ecs_entity_t rel = ecs_pair_first(world, id); + if (ecs_is_valid(world, rel)) { + if (ecs_has_id(world, rel, EcsTag)) { + return true; + } + } else { + /* During bootstrap it's possible that not all ids are valid + * yet. Using ecs_get_typeid will ensure correct values are + * returned for only those components initialized during + * bootstrap, while still asserting if another invalid id + * is provided. */ + if (ecs_get_typeid(world, id) == 0) { + return true; + } + } + } else { + /* If relationship is wildcard id is not guaranteed to be a tag */ + } + } + } else { + if (ecs_get_typeid(world, id) == 0) { + return true; + } + } + + return false; +} + +bool ecs_id_is_union( + const ecs_world_t *world, + ecs_id_t id) +{ + if (!ECS_IS_PAIR(id)) { + return false; + } else if (ECS_PAIR_FIRST(id) == EcsUnion) { + return true; + } else { + ecs_entity_t first = ecs_pair_first(world, id); + if (ecs_has_id(world, first, EcsUnion)) { + return true; + } + } + + return false; +} + +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_entity_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!id) { + return 0; + } + + int32_t count = 0; + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = id, + .src.flags = EcsSelf, + .flags = EcsTermMatchDisabled|EcsTermMatchPrefab + }); + + it.flags |= EcsIterNoData; + it.flags |= EcsIterEvalTables; + + while (ecs_term_next(&it)) { + count += it.count; + } + + return count; +error: + return 0; +} + +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled) +{ + if (ecs_has_id(world, entity, EcsPrefab)) { + /* If entity is a type, enable/disable all entities in the type */ + const ecs_type_t *type = ecs_get_type(world, entity); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *ids = type->array; + int32_t i, count = type->count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { + continue; + } + ecs_enable(world, id, enabled); + } + } else { + if (enabled) { + ecs_remove_id(world, entity, EcsDisabled); + } else { + ecs_add_id(world, entity, EcsDisabled); + } + } +} + +bool ecs_defer_begin( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_begin(world, stage); +error: + return false; +} + +bool ecs_defer_end( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_end(world, stage); +error: + return false; +} + +void ecs_defer_suspend( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer > 0, ECS_INVALID_OPERATION, NULL); + stage->defer = -stage->defer; +error: + return; +} + +void ecs_defer_resume( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, NULL); + stage->defer = -stage->defer; +error: + return; +} + +const char* ecs_id_flag_str( + ecs_entity_t entity) +{ + if (ECS_HAS_ID_FLAG(entity, PAIR)) { + return "PAIR"; + } else + if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { + return "TOGGLE"; + } else + if (ECS_HAS_ID_FLAG(entity, AND)) { + return "AND"; + } else + if (ECS_HAS_ID_FLAG(entity, OVERRIDE)) { + return "OVERRIDE"; + } else { + return "UNKNOWN"; + } +} + +void ecs_id_str_buf( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); + ecs_strbuf_appendch(buf, '|'); + } + + if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); + ecs_strbuf_appendch(buf, '|'); + } + + if (ECS_HAS_ID_FLAG(id, AND)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); + ecs_strbuf_appendch(buf, '|'); + } + + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t obj = ECS_PAIR_SECOND(id); + + ecs_entity_t e; + if ((e = ecs_get_alive(world, rel))) { + rel = e; + } + if ((e = ecs_get_alive(world, obj))) { + obj = e; + } + + ecs_strbuf_appendch(buf, '('); + ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_entity_t e = id & ECS_COMPONENT_MASK; + ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); + } + +error: + return; +} + +char* ecs_id_str( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_id_str_buf(world, id, &buf); + return ecs_strbuf_get(&buf); +} + +static +void ecs_type_str_buf( + const ecs_world_t *world, + const ecs_type_t *type, + ecs_strbuf_t *buf) +{ + ecs_entity_t *ids = type->array; + int32_t i, count = type->count; + + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; + + if (i) { + ecs_strbuf_appendch(buf, ','); + ecs_strbuf_appendch(buf, ' '); + } + + if (id == 1) { + ecs_strbuf_appendlit(buf, "Component"); + } else { + ecs_id_str_buf(world, id, buf); + } + } +} + +char* ecs_type_str( + const ecs_world_t *world, + const ecs_type_t *type) +{ + if (!type) { + return ecs_os_strdup(""); + } + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_type_str_buf(world, type, &buf); + return ecs_strbuf_get(&buf); +} + +char* ecs_table_str( + const ecs_world_t *world, + const ecs_table_t *table) +{ + if (table) { + return ecs_type_str(world, &table->type); + } else { + return NULL; + } +} + +char* ecs_entity_str( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); + + ecs_strbuf_appendlit(&buf, " ["); + const ecs_type_t *type = ecs_get_type(world, entity); + if (type) { + ecs_type_str_buf(world, type, &buf); + } + ecs_strbuf_appendch(&buf, ']'); + + return ecs_strbuf_get(&buf); +error: + return NULL; +} + +static +void flecs_flush_bulk_new( + ecs_world_t *world, + ecs_cmd_t *cmd) +{ + ecs_entity_t *entities = cmd->is._n.entities; + + if (cmd->id) { + int i, count = cmd->is._n.count; + for (i = 0; i < count; i ++) { + flecs_entities_ensure(world, entities[i]); + flecs_add_id(world, entities[i], cmd->id); + } + } + + ecs_os_free(entities); +} + +static +void flecs_dtor_value( + ecs_world_t *world, + ecs_id_t id, + void *value, + int32_t count) +{ + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + ecs_size_t size = ti->size; + void *ptr; + int i; + for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { + dtor(ptr, 1, ti); + } + } +} + +static +void flecs_free_cmd_event( + ecs_world_t *world, + ecs_event_desc_t *desc) +{ + if (desc->ids) { + flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); + } + + /* Safe const cast, command makes a copy of type object */ + flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), + ecs_type_t); + + if (desc->param) { + flecs_dtor_value(world, desc->event, + /* Safe const cast, command makes copy of value */ + ECS_CONST_CAST(void*, desc->param), 1); + } +} + +static +void flecs_discard_cmd( + ecs_world_t *world, + ecs_cmd_t *cmd) +{ + if (cmd->kind == EcsCmdBulkNew) { + ecs_os_free(cmd->is._n.entities); + } else if (cmd->kind == EcsCmdEvent) { + flecs_free_cmd_event(world, cmd->is._1.value); + } else { + ecs_assert(cmd->kind != EcsCmdEvent, ECS_INTERNAL_ERROR, NULL); + void *value = cmd->is._1.value; + if (value) { + flecs_dtor_value(world, cmd->id, value, 1); + flecs_stack_free(value, cmd->is._1.size); + } + } +} + +static +bool flecs_remove_invalid( + ecs_world_t *world, + ecs_id_t id, + ecs_id_t *id_out) +{ + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + if (!flecs_entities_is_valid(world, rel)) { + /* After relationship is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + *id_out = 0; + return true; + } else { + ecs_entity_t obj = ECS_PAIR_SECOND(id); + if (!flecs_entities_is_valid(world, obj)) { + /* Check the relationship's policy for deleted objects */ + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(rel, EcsWildcard)); + if (idr) { + ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(idr->flags); + if (action == EcsDelete) { + /* Entity should be deleted, don't bother checking + * other ids */ + return false; + } else if (action == EcsPanic) { + /* If policy is throw this object should not have + * been deleted */ + flecs_throw_invalid_delete(world, id); + } else { + *id_out = 0; + return true; + } + } else { + *id_out = 0; + return true; + } + } + } + } else { + id &= ECS_COMPONENT_MASK; + if (!flecs_entities_is_valid(world, id)) { + /* After relationship is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + *id_out = 0; + return true; + } + } + + return true; +} + +static +ecs_table_t* flecs_cmd_batch_add_diff( + ecs_world_t *world, + ecs_table_t *dst, + ecs_table_t *cur, + ecs_table_t *prev) +{ + int32_t p = 0, p_count = prev->type.count; + int32_t c = 0, c_count = cur->type.count; + ecs_id_t *p_ids = prev->type.array; + ecs_id_t *c_ids = cur->type.array; + + for (; c < c_count;) { + ecs_id_t c_id = c_ids[c]; + ecs_id_t p_id = p_ids[p]; + + if (p_id < c_id) { + /* Previous id no longer exists in new table, so it was removed */ + dst = ecs_table_remove_id(world, dst, p_id); + } + if (c_id < p_id) { + /* Current id didn't exist in previous table, so it was added */ + dst = ecs_table_add_id(world, dst, c_id); + } + + c += c_id <= p_id; + p += p_id <= c_id; + if (p == p_count) { + break; + } + } + + /* Remainder */ + for (; p < p_count; p ++) { + ecs_id_t p_id = p_ids[p]; + dst = ecs_table_remove_id(world, dst, p_id); + } + for (; c < c_count; c ++) { + ecs_id_t c_id = c_ids[c]; + dst = ecs_table_add_id(world, dst, c_id); + } + + return dst; +} + +static +void flecs_cmd_batch_for_entity( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, + ecs_entity_t entity, + ecs_cmd_t *cmds, + int32_t start) +{ + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = NULL; + if (r) { + table = r->table; + } + + world->info.cmd.batched_entity_count ++; + + ecs_table_t *start_table = table; + ecs_cmd_t *cmd; + int32_t next_for_entity; + ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ + int32_t cur = start; + ecs_id_t id; + bool has_set = false; + + do { + cmd = &cmds[cur]; + id = cmd->id; + next_for_entity = cmd->next_for_entity; + if (next_for_entity < 0) { + /* First command for an entity has a negative index, flip sign */ + next_for_entity *= -1; + } + + /* Check if added id is still valid (like is the parent of a ChildOf + * pair still alive), if not run cleanup actions for entity */ + if (id) { + if (flecs_remove_invalid(world, id, &id)) { + if (!id) { + /* Entity should remain alive but id should not be added */ + cmd->kind = EcsCmdSkip; + continue; + } + /* Entity should remain alive and id is still valid */ + } else { + /* Id was no longer valid and had a Delete policy */ + cmd->kind = EcsCmdSkip; + ecs_delete(world, entity); + flecs_table_diff_builder_clear(diff); + return; + } + } + + ecs_cmd_kind_t kind = cmd->kind; + switch(kind) { + case EcsCmdAddModified: + /* Add is batched, but keep Modified */ + cmd->kind = EcsCmdModified; + + /* fall through */ + case EcsCmdAdd: + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + break; + case EcsCmdModified: + if (start_table) { + /* If a modified was inserted for an existing component, the value + * of the component could have been changed. If this is the case, + * call on_set hooks before the OnAdd/OnRemove observers are invoked + * when moving the entity to a different table. + * This ensures that if OnAdd/OnRemove observers access the modified + * component value, the on_set hook has had the opportunity to + * run first to set any computed values of the component. */ + int32_t row = ECS_RECORD_TO_ROW(r->row); + flecs_component_ptr_t ptr = flecs_get_component_ptr( + world, start_table, row, cmd->id); + if (ptr.ptr) { + const ecs_type_info_t *ti = ptr.ti; + ecs_iter_action_t on_set; + if ((on_set = ti->hooks.on_set)) { + ecs_table_t *prev_table = r->table; + ecs_defer_begin(world); + flecs_invoke_hook(world, start_table, 1, row, &entity, + ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); + ecs_defer_end(world); + + /* Don't run on_set hook twice, but make sure to still + * invoke observers. */ + cmd->kind = EcsCmdModifiedNoHook; + + /* If entity changed tables in hook, add difference to + * destination table, so we don't lose the side effects + * of the hook. */ + if (r->table != prev_table) { + table = flecs_cmd_batch_add_diff( + world, table, r->table, prev_table); + } + } + } + } + break; + case EcsCmdSet: + case EcsCmdMut: + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + has_set = true; + break; + case EcsCmdEmplace: + /* Don't add for emplace, as this requires a special call to ensure + * the constructor is not invoked for the component */ + break; + case EcsCmdRemove: + table = flecs_find_table_remove(world, table, id, diff); + world->info.cmd.batched_command_count ++; + break; + case EcsCmdClear: + if (table) { + ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, + &diff->removed, ecs_id_t, table->type.count); + ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, + table->type.count); + } + table = &world->store.root; + world->info.cmd.batched_command_count ++; + break; + case EcsCmdClone: + case EcsCmdBulkNew: + case EcsCmdPath: + case EcsCmdDelete: + case EcsCmdOnDeleteAction: + case EcsCmdEnable: + case EcsCmdDisable: + case EcsCmdEvent: + case EcsCmdSkip: + case EcsCmdModifiedNoHook: + break; + } + + /* Add, remove and clear operations can be skipped since they have no + * side effects besides adding/removing components */ + if (kind == EcsCmdAdd || kind == EcsCmdRemove || kind == EcsCmdClear) { + cmd->kind = EcsCmdSkip; + } + } while ((cur = next_for_entity)); + + /* Move entity to destination table in single operation */ + flecs_table_diff_build_noalloc(diff, &table_diff); + flecs_defer_begin(world, &world->stages[0]); + flecs_commit(world, entity, r, table, &table_diff, true, 0); + flecs_defer_end(world, &world->stages[0]); + flecs_table_diff_builder_clear(diff); + + /* If the batch contains set commands, copy the component value from the + * temporary command storage to the actual component storage before OnSet + * observers are invoked. This ensures that for multi-component OnSet + * observers all components are assigned a valid value before the observer + * is invoked. + * This only happens for entities that didn't have the assigned component + * yet, as for entities that did have the component already the value will + * have been assigned directly to the component storage. */ + if (has_set) { + cur = start; + do { + cmd = &cmds[cur]; + next_for_entity = cmd->next_for_entity; + if (next_for_entity < 0) { + next_for_entity *= -1; + } + switch(cmd->kind) { + case EcsCmdSet: + case EcsCmdMut: { + flecs_component_ptr_t ptr = {0}; + if (r->table) { + ptr = flecs_get_component_ptr(world, + r->table, ECS_RECORD_TO_ROW(r->row), cmd->id); + } + + /* It's possible that even though the component was set, the + * command queue also contained a remove command, so before we + * do anything ensure the entity actually has the component. */ + if (ptr.ptr) { + const ecs_type_info_t *ti = ptr.ti; + ecs_move_t move = ti->hooks.move; + if (move) { + move(ptr.ptr, cmd->is._1.value, 1, ti); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + dtor(cmd->is._1.value, 1, ti); + cmd->is._1.value = NULL; + } + } else { + ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); + } + if (cmd->kind == EcsCmdSet) { + /* A set operation is add + copy + modified. We just did + * the add the copy, so the only thing that's left is a + * modified command, which will call the OnSet + * observers. */ + cmd->kind = EcsCmdModified; + } else { + /* If this was a get_mut, nothing's left to be done */ + cmd->kind = EcsCmdSkip; + } + } else { + /* The entity no longer has the component which means that + * there was a remove command for the component in the + * command queue. In that case skip the command. */ + cmd->kind = EcsCmdSkip; + } + break; + } + case EcsCmdClone: + case EcsCmdBulkNew: + case EcsCmdAdd: + case EcsCmdRemove: + case EcsCmdEmplace: + case EcsCmdModified: + case EcsCmdModifiedNoHook: + case EcsCmdAddModified: + case EcsCmdPath: + case EcsCmdDelete: + case EcsCmdClear: + case EcsCmdOnDeleteAction: + case EcsCmdEnable: + case EcsCmdDisable: + case EcsCmdEvent: + case EcsCmdSkip: + break; + } + } while ((cur = next_for_entity)); + } +} + +/* Leave safe section. Run all deferred commands. */ +bool flecs_defer_end( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); + + if (stage->defer < 0) { + /* Defer suspending makes it possible to do operations on the storage + * without flushing the commands in the queue */ + return false; + } + + ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); + + if (!--stage->defer) { + /* Test whether we're flushing to another queue or whether we're + * flushing to the storage */ + bool merge_to_world = false; + if (ecs_poly_is(world, ecs_world_t)) { + merge_to_world = world->stages[0].defer == 0; + } + + ecs_stage_t *dst_stage = flecs_stage_from_world(&world); + ecs_commands_t *commands = stage->cmd; + ecs_vec_t *queue = &commands->queue; + + if (ecs_vec_count(queue)) { + ecs_cmd_t *cmds = ecs_vec_first(queue); + int32_t i, count = ecs_vec_count(queue); + + ecs_table_diff_builder_t diff; + flecs_table_diff_builder_init(world, &diff); + flecs_commands_push(stage); + + for (i = 0; i < count; i ++) { + ecs_cmd_t *cmd = &cmds[i]; + ecs_entity_t e = cmd->entity; + bool is_alive = flecs_entities_is_alive(world, e); + + /* A negative index indicates the first command for an entity */ + if (merge_to_world && (cmd->next_for_entity < 0)) { + /* Batch commands for entity to limit archetype moves */ + if (is_alive) { + flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); + } else { + world->info.cmd.discard_count ++; + } + } + + /* Invalidate entry */ + if (cmd->entry) { + cmd->entry->first = -1; + } + + /* 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. */ + ecs_cmd_kind_t kind = cmd->kind; + if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { + world->info.cmd.discard_count ++; + flecs_discard_cmd(world, cmd); + continue; + } + + ecs_id_t id = cmd->id; + + switch(kind) { + case EcsCmdAdd: + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + if (flecs_remove_invalid(world, id, &id)) { + if (id) { + world->info.cmd.add_count ++; + flecs_add_id(world, e, id); + } else { + world->info.cmd.discard_count ++; + } + } else { + world->info.cmd.discard_count ++; + ecs_delete(world, e); + } + break; + case EcsCmdRemove: + flecs_remove_id(world, e, id); + world->info.cmd.remove_count ++; + break; + case EcsCmdClone: + ecs_clone(world, e, id, cmd->is._1.clone_value); + break; + case EcsCmdSet: + flecs_move_ptr_w_id(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.set_count ++; + break; + case EcsCmdEmplace: + if (merge_to_world) { + ecs_emplace_id(world, e, id); + } + flecs_move_ptr_w_id(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.get_mut_count ++; + break; + case EcsCmdMut: + flecs_move_ptr_w_id(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.get_mut_count ++; + break; + case EcsCmdModified: + flecs_modified_id_if(world, e, id, true); + world->info.cmd.modified_count ++; + break; + case EcsCmdModifiedNoHook: + flecs_modified_id_if(world, e, id, false); + world->info.cmd.modified_count ++; + break; + case EcsCmdAddModified: + flecs_add_id(world, e, id); + flecs_modified_id_if(world, e, id, true); + world->info.cmd.add_count ++; + world->info.cmd.modified_count ++; + break; + case EcsCmdDelete: { + ecs_delete(world, e); + world->info.cmd.delete_count ++; + break; + } + case EcsCmdClear: + ecs_clear(world, e); + world->info.cmd.clear_count ++; + break; + case EcsCmdOnDeleteAction: + ecs_defer_begin(world); + flecs_on_delete(world, id, e, false); + ecs_defer_end(world); + world->info.cmd.other_count ++; + break; + case EcsCmdEnable: + ecs_enable_id(world, e, id, true); + world->info.cmd.other_count ++; + break; + case EcsCmdDisable: + ecs_enable_id(world, e, id, false); + world->info.cmd.other_count ++; + break; + case EcsCmdBulkNew: + flecs_flush_bulk_new(world, cmd); + world->info.cmd.other_count ++; + continue; + case EcsCmdPath: + ecs_ensure(world, e); + if (cmd->id) { + ecs_add_pair(world, e, EcsChildOf, cmd->id); + } + ecs_set_name(world, e, cmd->is._1.value); + ecs_os_free(cmd->is._1.value); + cmd->is._1.value = NULL; + break; + case EcsCmdEvent: { + ecs_event_desc_t *desc = cmd->is._1.value; + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_emit((ecs_world_t*)stage, desc); + flecs_free_cmd_event(world, desc); + break; + } + case EcsCmdSkip: + break; + } + + if (cmd->is._1.value) { + flecs_stack_free(cmd->is._1.value, cmd->is._1.size); + } + } + + flecs_stack_reset(&commands->stack); + ecs_vec_clear(queue); + flecs_commands_pop(stage); + + flecs_table_diff_builder_fini(world, &diff); + } + + return true; + } + + return false; +} + +/* Delete operations from queue without executing them. */ +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!--stage->defer) { + ecs_vec_t commands = stage->cmd->queue; + + if (ecs_vec_count(&commands)) { + ecs_cmd_t *cmds = ecs_vec_first(&commands); + int32_t i, count = ecs_vec_count(&commands); + for (i = 0; i < count; i ++) { + flecs_discard_cmd(world, &cmds[i]); + } + + ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); + + ecs_vec_clear(&commands); + flecs_stack_reset(&stage->cmd->stack); + flecs_sparse_clear(&stage->cmd->entries); + } + + return true; + } + +error: + return false; +} + +/** + * @file entity_filter.c + * @brief Filters that are applied to entities in a table. + * + * After a table has been matched by a query, additional filters may have to + * be applied before returning entities to the application. The two scenarios + * under which this happens are queries for union relationship pairs (entities + * for multiple targets are stored in the same table) and toggles (components + * that are enabled/disabled with a bitset). + */ + + +static +int flecs_entity_filter_find_smallest_term( + ecs_table_t *table, + ecs_entity_filter_iter_t *iter) +{ + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_switch_term_t *sw_terms = ecs_vec_first(&iter->entity_filter->sw_terms); + int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); + int32_t min = INT_MAX, index = 0; + + for (i = 0; i < count; i ++) { + /* The array with sparse queries for the matched table */ + flecs_switch_term_t *sparse_column = &sw_terms[i]; + + /* Pointer to the switch column struct of the table */ + ecs_switch_t *sw = sparse_column->sw_column; + + /* If the sparse column pointer hadn't been retrieved yet, do it now */ + if (!sw) { + /* Get the table column index from the signature column index */ + int32_t table_column_index = iter->columns[ + sparse_column->signature_column_index]; + + /* Translate the table column index to switch column index */ + table_column_index -= table->_->sw_offset; + ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + + /* Get the sparse column */ + sw = sparse_column->sw_column = + &table->_->sw_columns[table_column_index - 1]; + } + + /* Find the smallest column */ + int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); + if (case_count < min) { + min = case_count; + index = i + 1; + } + } + + return index; +} + +static +int flecs_entity_filter_switch_next( + ecs_table_t *table, + ecs_entity_filter_iter_t *iter, + bool filter) +{ + bool first_iteration = false; + int32_t switch_smallest; + + if (!(switch_smallest = iter->sw_smallest)) { + switch_smallest = iter->sw_smallest = + flecs_entity_filter_find_smallest_term(table, iter); + first_iteration = true; + } + + switch_smallest -= 1; + + flecs_switch_term_t *columns = ecs_vec_first(&iter->entity_filter->sw_terms); + flecs_switch_term_t *column = &columns[switch_smallest]; + ecs_switch_t *sw, *sw_smallest = column->sw_column; + ecs_entity_t case_smallest = column->sw_case; + + /* Find next entity to iterate in sparse column */ + int32_t first, sparse_first = iter->sw_offset; + + if (!filter) { + if (first_iteration) { + first = flecs_switch_first(sw_smallest, case_smallest); + } else { + first = flecs_switch_next(sw_smallest, sparse_first); + } + } else { + int32_t cur_first = iter->range.offset, cur_count = iter->range.count; + first = cur_first; + while (flecs_switch_get(sw_smallest, first) != case_smallest) { + first ++; + if (first >= (cur_first + cur_count)) { + first = -1; + break; + } + } + } + + if (first == -1) { + goto done; + } + + /* Check if entity matches with other sparse columns, if any */ + int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); + do { + for (i = 0; i < count; i ++) { + if (i == switch_smallest) { + /* Already validated this one */ + continue; + } + + column = &columns[i]; + sw = column->sw_column; + + if (flecs_switch_get(sw, first) != column->sw_case) { + first = flecs_switch_next(sw_smallest, first); + if (first == -1) { + goto done; + } + } + } + } while (i != count); + + iter->range.offset = iter->sw_offset = first; + iter->range.count = 1; + + return 0; +done: + /* Iterated all elements in the sparse list, we should move to the + * next matched table. */ + iter->sw_smallest = 0; + iter->sw_offset = 0; + + return -1; +} + +#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) + +static +int flecs_entity_filter_bitset_next( + ecs_table_t *table, + ecs_entity_filter_iter_t *iter) +{ + /* 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_vec_count(&iter->entity_filter->bs_terms); + flecs_bitset_term_t *terms = ecs_vec_first(&iter->entity_filter->bs_terms); + int32_t bs_offset = table->_->bs_offset; + int32_t first = iter->bs_offset; + int32_t last = 0; + + for (i = 0; i < count; i ++) { + flecs_bitset_term_t *column = &terms[i]; + ecs_bitset_t *bs = terms[i].bs_column; + + if (!bs) { + int32_t index = column->column_index; + ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); + bs = &table->_->bs_columns[index - bs_offset]; + terms[i].bs_column = bs; + } + + 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 - first)) { + elem_count = (bs_elem_count - first); + if (!elem_count) { + iter->bs_offset = 0; + goto done; + } + } + + iter->range.offset = first; + iter->range.count = elem_count; + iter->bs_offset = first; + } + + /* Keep track of last processed element for iteration */ + iter->bs_offset = last; + + return 0; +done: + iter->sw_smallest = 0; + iter->sw_offset = 0; + return -1; +} + +#undef BS_MAX + +static +int32_t flecs_get_flattened_target( + ecs_world_t *world, + EcsTarget *cur, + ecs_entity_t rel, + ecs_id_t id, + ecs_entity_t *src_out, + ecs_table_record_t **tr_out) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return -1; + } + + ecs_record_t *r = cur->target; + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = r->table; + if (!table) { + return -1; + } + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (tr) { + *src_out = ecs_record_get_entity(r); + *tr_out = tr; + return tr->index; + } + + if (table->flags & EcsTableHasTarget) { + int32_t col = table->column_map[table->_->ft_offset]; + ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); + EcsTarget *next = table->data.columns[col].data.array; + next = ECS_ELEM_T(next, EcsTarget, ECS_RECORD_TO_ROW(r->row)); + return flecs_get_flattened_target( + world, next, rel, id, src_out, tr_out); + } + + return ecs_search_relation( + world, table, 0, id, rel, EcsSelf|EcsUp, src_out, NULL, tr_out); +} + +void flecs_entity_filter_init( + ecs_world_t *world, + ecs_entity_filter_t **entity_filter, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_id_t *ids, + int32_t *columns) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(entity_filter != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &world->allocator; + ecs_entity_filter_t ef; + ecs_os_zeromem(&ef); + ecs_vec_t *sw_terms = &ef.sw_terms; + ecs_vec_t *bs_terms = &ef.bs_terms; + ecs_vec_t *ft_terms = &ef.ft_terms; + if (*entity_filter) { + ef.sw_terms = (*entity_filter)->sw_terms; + ef.bs_terms = (*entity_filter)->bs_terms; + ef.ft_terms = (*entity_filter)->ft_terms; + } + ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t); + ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t); + ecs_vec_reset_t(a, ft_terms, flecs_flat_table_term_t); + ecs_term_t *terms = filter->terms; + int32_t i, term_count = filter->term_count; + bool has_filter = false; + ef.flat_tree_column = -1; + + /* Look for union fields */ + if (table->flags & EcsTableHasUnion) { + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } + + ecs_id_t id = terms[i].id; + if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) { + continue; + } + + int32_t field = terms[i].field_index; + int32_t column = columns[field]; + if (column <= 0) { + continue; + } + + ecs_id_t table_id = table->type.array[column - 1]; + if (ECS_PAIR_FIRST(table_id) != EcsUnion) { + continue; + } + + flecs_switch_term_t *el = ecs_vec_append_t(a, sw_terms, + flecs_switch_term_t); + el->signature_column_index = field; + el->sw_case = ecs_pair_second(world, id); + el->sw_column = NULL; + ids[field] = id; + has_filter = true; + } + } + + /* Look for disabled fields */ + if (table->flags & EcsTableHasToggle) { + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } + + int32_t field = terms[i].field_index; + ecs_id_t id = ids[field]; + ecs_id_t bs_id = ECS_TOGGLE | id; + int32_t bs_index = ecs_table_get_type_index(world, table, bs_id); + + if (bs_index != -1) { + flecs_bitset_term_t *bc = ecs_vec_append_t(a, bs_terms, + flecs_bitset_term_t); + bc->column_index = bs_index; + bc->bs_column = NULL; + has_filter = true; + } + } + } + + /* Look for flattened fields */ + if (table->flags & EcsTableHasTarget) { + const ecs_table_record_t *tr = flecs_table_record_get(world, table, + ecs_pair_t(EcsTarget, EcsWildcard)); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t column = tr->index; + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t rel = ecs_pair_second(world, table->type.array[column]); + + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } + + if (terms[i].src.trav == rel) { + ef.flat_tree_column = table->column_map[column]; + ecs_assert(ef.flat_tree_column != -1, + ECS_INTERNAL_ERROR, NULL); + has_filter = true; + + flecs_flat_table_term_t *term = ecs_vec_append_t( + a, ft_terms, flecs_flat_table_term_t); + term->field_index = terms[i].field_index; + term->term = &terms[i]; + ecs_os_zeromem(&term->monitor); + } + } + } + + if (has_filter) { + if (!*entity_filter) { + *entity_filter = ecs_os_malloc_t(ecs_entity_filter_t); + } + ecs_assert(*entity_filter != NULL, ECS_OUT_OF_MEMORY, NULL); + **entity_filter = ef; + } +} + +void flecs_entity_filter_fini( + ecs_world_t *world, + ecs_entity_filter_t *ef) +{ + if (!ef) { + return; + } + + ecs_allocator_t *a = &world->allocator; + + flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); + int32_t i, term_count = ecs_vec_count(&ef->ft_terms); + for (i = 0; i < term_count; i ++) { + ecs_vec_fini_t(NULL, &fields[i].monitor, flecs_flat_monitor_t); + } + + ecs_vec_fini_t(a, &ef->sw_terms, flecs_switch_term_t); + ecs_vec_fini_t(a, &ef->bs_terms, flecs_bitset_term_t); + ecs_vec_fini_t(a, &ef->ft_terms, flecs_flat_table_term_t); + ecs_os_free(ef); +} + +int flecs_entity_filter_next( + ecs_entity_filter_iter_t *it) +{ + ecs_table_t *table = it->range.table; + flecs_switch_term_t *sw_terms = ecs_vec_first(&it->entity_filter->sw_terms); + flecs_bitset_term_t *bs_terms = ecs_vec_first(&it->entity_filter->bs_terms); + ecs_entity_filter_t *ef = it->entity_filter; + int32_t flat_tree_column = ef->flat_tree_column; + ecs_table_range_t *range = &it->range; + int32_t range_end = range->offset + range->count; + int result = EcsIterNext; + bool found = false; + + do { + found = false; + + if (bs_terms) { + if (flecs_entity_filter_bitset_next(table, it) == -1) { + /* No more enabled components for table */ + it->bs_offset = 0; + break; + } else { + result = EcsIterYield; + found = true; + } + } + + if (sw_terms) { + if (flecs_entity_filter_switch_next(table, it, found) == -1) { + /* No more elements in sparse column */ + if (found) { + /* Try again */ + result = EcsIterNext; + found = false; + } else { + /* Nothing found */ + it->bs_offset = 0; + break; + } + } else { + result = EcsIterYield; + found = true; + it->bs_offset = range->offset + range->count; + } + } + + if (flat_tree_column != -1) { + bool first_for_table = it->prev != table; + ecs_iter_t *iter = it->it; + ecs_world_t *world = iter->real_world; + EcsTarget *ft = table->data.columns[flat_tree_column].data.array; + int32_t ft_offset; + int32_t ft_count; + + if (first_for_table) { + ft_offset = it->flat_tree_offset = range->offset; + it->target_count = 1; + } else { + it->flat_tree_offset += ft[it->flat_tree_offset].count; + ft_offset = it->flat_tree_offset; + it->target_count ++; + } + + ecs_assert(ft_offset < ecs_table_count(table), + ECS_INTERNAL_ERROR, NULL); + + EcsTarget *cur = &ft[ft_offset]; + ft_count = cur->count; + bool is_last = (ft_offset + ft_count) >= range_end; + + int32_t i, field_count = ecs_vec_count(&ef->ft_terms); + flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); + for (i = 0; i < field_count; i ++) { + flecs_flat_table_term_t *field = &fields[i]; + ecs_vec_init_if_t(&field->monitor, flecs_flat_monitor_t); + int32_t field_index = field->field_index; + ecs_id_t id = it->it->ids[field_index]; + ecs_id_t flat_pair = table->type.array[flat_tree_column]; + ecs_entity_t rel = ECS_PAIR_FIRST(flat_pair); + ecs_entity_t tgt; + ecs_table_record_t *tr; + int32_t tgt_col = flecs_get_flattened_target( + world, cur, rel, id, &tgt, &tr); + if (tgt_col != -1) { + iter->sources[field_index] = tgt; + iter->columns[field_index] = /* encode flattened field */ + -(iter->field_count + tgt_col + 1); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep track of maximum value encountered in target table + * dirty state so this doesn't have to be recomputed when + * synchronizing the query monitor. */ + ecs_vec_set_min_count_zeromem_t(NULL, &field->monitor, + flecs_flat_monitor_t, it->target_count); + ecs_table_t *tgt_table = tr->hdr.table; + int32_t *ds = flecs_table_get_dirty_state(world, tgt_table); + ecs_assert(ds != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_get_t(&field->monitor, flecs_flat_monitor_t, + it->target_count - 1)->table_state = ds[tgt_col + 1]; + } else { + if (field->term->oper == EcsOptional) { + iter->columns[field_index] = 0; + iter->ptrs[field_index] = NULL; + } else { + it->prev = NULL; + break; + } + } + } + if (i != field_count) { + if (is_last) { + break; + } + } else { + found = true; + if ((ft_offset + ft_count) == range_end) { + result = EcsIterNextYield; + } else { + result = EcsIterYield; + } + } + + range->offset = ft_offset; + range->count = ft_count; + it->prev = table; + } + } while (!found); + + it->prev = table; + + if (!found) { + return EcsIterNext; + } else { + return result; + } +} + +/** + * @file entity_name.c + * @brief Functions for working with named entities. + */ + +#include + +#define ECS_NAME_BUFFER_LENGTH (64) + +static +bool flecs_path_append( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t cur = 0; + const char *name = NULL; + ecs_size_t name_len = 0; + + if (child && ecs_is_alive(world, child)) { + cur = ecs_get_target(world, child, EcsChildOf, 0); + if (cur) { + ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); + if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { + flecs_path_append(world, parent, cur, sep, prefix, buf); + if (!sep[1]) { + ecs_strbuf_appendch(buf, sep[0]); + } else { + ecs_strbuf_appendstr(buf, sep); + } + } + } else if (prefix && prefix[0]) { + if (!prefix[1]) { + ecs_strbuf_appendch(buf, prefix[0]); + } else { + ecs_strbuf_appendstr(buf, prefix); + } + } + + const EcsIdentifier *id = ecs_get_pair( + world, child, EcsIdentifier, EcsName); + if (id) { + name = id->value; + name_len = id->length; + } + } + + if (name) { + ecs_strbuf_appendstrn(buf, name, name_len); + } else { + ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); + } + + return cur != 0; +} + +bool flecs_name_is_id( + const char *name) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!isdigit(name[0])) { + return false; + } + + ecs_size_t i, length = ecs_os_strlen(name); + for (i = 1; i < length; i ++) { + char ch = name[i]; + + if (!isdigit(ch)) { + break; + } + } + + return i >= length; +} + +ecs_entity_t flecs_name_to_id( + const ecs_world_t *world, + const char *name) +{ + int64_t result = atoll(name); + ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result); + if (alive) { + return alive; + } else { + if ((uint32_t)result == (uint64_t)result) { + return (ecs_entity_t)result; + } else { + return 0; + } + } +} + +static +ecs_entity_t flecs_get_builtin( + const char *name) +{ + if (name[0] == '.' && name[1] == '\0') { + return EcsThis; + } else if (name[0] == '*' && name[1] == '\0') { + return EcsWildcard; + } else if (name[0] == '_' && name[1] == '\0') { + return EcsAny; + } else if (name[0] == '$' && name[1] == '\0') { + return EcsVariable; + } + + return 0; +} + +static +bool flecs_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; + return true; + } else { + return false; + } +} + +static +const char* flecs_path_elem( + const char *path, + const char *sep, + int32_t *len) +{ + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t count = 0; + + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; + } + + ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); + + if (!template_nesting && flecs_is_sep(&ptr, sep)) { + break; + } + + count ++; + } + + if (len) { + *len = count; + } + + if (count) { + return ptr; + } else { + return NULL; + } +error: + return NULL; +} + +static +bool flecs_is_root_path( + const char *path, + const char *prefix) +{ + if (prefix) { + return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); + } else { + return false; + } +} + +static +ecs_entity_t flecs_get_parent_from_path( + const ecs_world_t *world, + ecs_entity_t parent, + const char **path_ptr, + const char *prefix, + bool new_entity) +{ + bool start_from_root = false; + const char *path = *path_ptr; + + if (flecs_is_root_path(path, prefix)) { + path += ecs_os_strlen(prefix); + parent = 0; + start_from_root = true; + } + + if (!start_from_root && !parent && new_entity) { + parent = ecs_get_scope(world); + } + + *path_ptr = path; + + return parent; +} + +static +void flecs_on_set_symbol(ecs_iter_t *it) { + EcsIdentifier *n = ecs_field(it, EcsIdentifier, 1); + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + flecs_name_index_ensure( + &world->symbols, e, n[i].value, n[i].length, n[i].hash); + } +} + +void flecs_bootstrap_hierarchy(ecs_world_t *world) { + ecs_observer(world, { + .entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), + .filter.terms[0] = { + .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), + .src.flags = EcsSelf + }, + .callback = flecs_on_set_symbol, + .events = {EcsOnSet}, + .yield_existing = true + }); +} + + +/* Public functions */ + +void ecs_get_path_w_sep_buf( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + if (child == EcsWildcard) { + ecs_strbuf_appendch(buf, '*'); + return; + } + if (child == EcsAny) { + ecs_strbuf_appendch(buf, '_'); + return; + } + + if (!sep) { + sep = "."; + } + + if (!child || parent != child) { + flecs_path_append(world, parent, child, sep, prefix, buf); + } else { + ecs_strbuf_appendstrn(buf, "", 0); + } + +error: + return; +} + +char* ecs_get_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); + return ecs_strbuf_get(&buf); +} + +ecs_entity_t ecs_lookup_child( + const ecs_world_t *world, + ecs_entity_t parent, + const char *name) +{ + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + if (flecs_name_is_id(name)) { + ecs_entity_t result = flecs_name_to_id(world, name); + if (result && ecs_is_alive(world, result)) { + if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { + return 0; + } + return result; + } + } + + ecs_id_t pair = ecs_childof(parent); + ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); + if (index) { + return flecs_name_index_find(index, name, 0, 0); + } else { + return 0; + } +error: + return 0; +} + +ecs_entity_t ecs_lookup( + const ecs_world_t *world, + const char *name) +{ + if (!name) { + return 0; + } + + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = flecs_get_builtin(name); + if (e) { + return e; + } + + if (flecs_name_is_id(name)) { + return flecs_name_to_id(world, name); + } + + e = flecs_name_index_find(&world->aliases, name, 0, 0); + if (e) { + return e; + } + + return ecs_lookup_child(world, 0, name); +error: + return 0; +} + +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *name, + bool lookup_as_path, + bool recursive) +{ + if (!name) { + return 0; + } + + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); + if (e) { + return e; + } + + if (lookup_as_path) { + return ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); + } + +error: + return 0; +} + +ecs_entity_t ecs_lookup_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix, + bool recursive) +{ + if (!path) { + return 0; + } + + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_world_t *stage = world; + world = ecs_get_world(world); + + ecs_entity_t e = flecs_get_builtin(path); + if (e) { + return e; + } + + e = flecs_name_index_find(&world->aliases, path, 0, 0); + if (e) { + return e; + } + + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr, *ptr_start; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; + ecs_entity_t cur; + bool lookup_path_search = false; + + const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); + const ecs_entity_t *lookup_path_cur = lookup_path; + while (lookup_path_cur && *lookup_path_cur) { + lookup_path_cur ++; + } + + if (!sep) { + sep = "."; + } + + parent = flecs_get_parent_from_path(stage, parent, &path, prefix, true); + + if (!sep[0]) { + return ecs_lookup_child(world, parent, path); + } + +retry: + cur = parent; + ptr_start = ptr = path; + + while ((ptr = flecs_path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } + + elem[len] = '\0'; + ptr_start = ptr; + + cur = ecs_lookup_child(world, cur, elem); + if (!cur) { + goto tail; + } + } + +tail: + if (!cur && recursive) { + if (!lookup_path_search) { + if (parent) { + parent = ecs_get_target(world, parent, EcsChildOf, 0); + goto retry; + } else { + lookup_path_search = true; + } + } + + if (lookup_path_search) { + if (lookup_path_cur != lookup_path) { + lookup_path_cur --; + parent = lookup_path_cur[0]; + goto retry; + } + } + } + + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; +error: + return 0; +} + +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_entity_t cur = stage->scope; + stage->scope = scope; + + return cur; +error: + return 0; +} + +ecs_entity_t ecs_get_scope( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->scope; +error: + return 0; +} + +ecs_entity_t* ecs_set_lookup_path( + ecs_world_t *world, + const ecs_entity_t *lookup_path) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + /* Safe: application owns lookup path */ + ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); + stage->lookup_path = lookup_path; + + return cur; +error: + return NULL; +} + +ecs_entity_t* ecs_get_lookup_path( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + /* Safe: application owns lookup path */ + return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); +error: + return NULL; +} + +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) +{ + ecs_poly_assert(world, ecs_world_t); + const char *old_prefix = world->info.name_prefix; + world->info.name_prefix = prefix; + return old_prefix; +} + +static +void flecs_add_path( + ecs_world_t *world, + bool defer_suspend, + ecs_entity_t parent, + ecs_entity_t entity, + const char *name) +{ + ecs_suspend_readonly_state_t srs; + ecs_world_t *real_world = NULL; + if (defer_suspend) { + real_world = flecs_suspend_readonly(world, &srs); + ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, parent); + } + + ecs_set_name(world, entity, name); + + if (defer_suspend) { + flecs_resume_readonly(real_world, &srs); + flecs_defer_path((ecs_stage_t*)world, parent, entity, name); + } +} + +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) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!sep) { + sep = "."; + } + + if (!path) { + if (!entity) { + entity = ecs_new_id(world); + } + + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); + } + + return entity; + } + + bool root_path = flecs_is_root_path(path, prefix); + parent = flecs_get_parent_from_path(world, parent, &path, prefix, !entity); + + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr = path; + const char *ptr_start = path; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; + + /* If we're in deferred/readonly mode suspend it, so that the name index is + * immediately updated. Without this, we could create multiple entities for + * the same name in a single command queue. */ + bool suspend_defer = ecs_poly_is(world, ecs_stage_t) && + (ecs_get_stage_count(world) <= 1); + ecs_entity_t cur = parent; + char *name = NULL; + + if (sep[0]) { + while ((ptr = flecs_path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } + + elem[len] = '\0'; + ptr_start = ptr; + + ecs_entity_t e = ecs_lookup_child(world, cur, elem); + if (!e) { + if (name) { + ecs_os_free(name); + } + + name = ecs_os_strdup(elem); + + /* If this is the last entity in the path, use the provided id */ + bool last_elem = false; + if (!flecs_path_elem(ptr, sep, NULL)) { + e = entity; + last_elem = true; + } + + if (!e) { + if (last_elem) { + ecs_entity_t prev = ecs_set_scope(world, 0); + e = ecs_new(world, 0); + ecs_set_scope(world, prev); + } else { + e = ecs_new_id(world); + } + } + + if (!cur && last_elem && root_path) { + ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); + } + + flecs_add_path(world, suspend_defer, cur, e, name); + } + + cur = e; + } + + if (entity && (cur != entity)) { + ecs_throw(ECS_ALREADY_DEFINED, name); + } + + if (name) { + ecs_os_free(name); + } + + if (elem != buff) { + ecs_os_free(elem); + } + } else { + flecs_add_path(world, suspend_defer, parent, entity, path); + } + + return cur; +error: + return 0; +} + +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); +} + +/** + * @file filter.c + * @brief Uncached query implementation. + * + * Uncached queries (filters) are stateless objects that do not cache their + * results. This file contains the creation and validation of uncached queries + * and code for query iteration. + * + * There file contains the implementation for term queries and filters. Term + * queries are uncached queries that only apply to a single term. Filters are + * uncached queries that support multiple terms. Filters are built on top of + * term queries: before iteration a filter will first find a "pivot" term (the + * term with the smallest number of elements), and create a term iterator for + * it. The output of that term iterator is then evaluated against the rest of + * the terms of the filter. + * + * Cached queries and observers are built using filters. + */ + +#include + +ecs_filter_t ECS_FILTER_INIT = { .hdr = { .magic = ecs_filter_t_magic }}; + +/* Helper type for passing around context required for error messages */ +typedef struct { + const ecs_world_t *world; + ecs_filter_t *filter; + ecs_term_t *term; + int32_t term_index; +} ecs_filter_finalize_ctx_t; + +static +char* flecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_filter_finalize_ctx_t *ctx, + int32_t *term_start_out); + +static +void flecs_filter_error( + const ecs_filter_finalize_ctx_t *ctx, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + + int32_t term_start = 0; + + char *expr = NULL; + if (ctx->filter) { + expr = flecs_filter_str(ctx->world, ctx->filter, ctx, &term_start); + } else { + expr = ecs_term_str(ctx->world, ctx->term); + } + const char *name = NULL; + if (ctx->filter && ctx->filter->entity) { + name = ecs_get_name(ctx->filter->world, ctx->filter->entity); + } + ecs_parser_errorv(name, expr, term_start, fmt, args); + ecs_os_free(expr); + + va_end(args); +} + +static +int flecs_term_id_finalize_flags( + ecs_term_id_t *term_id, + ecs_filter_finalize_ctx_t *ctx) +{ + if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { + flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); + return -1; + } + + if (!(term_id->flags & (EcsIsEntity|EcsIsVariable|EcsIsName))) { + if (term_id->id || term_id->name) { + if (term_id->id == EcsThis || + term_id->id == EcsWildcard || + term_id->id == EcsAny || + term_id->id == EcsVariable) + { + /* Builtin variable ids default to variable */ + term_id->flags |= EcsIsVariable; + } else { + term_id->flags |= EcsIsEntity; + } + } + } + + if (term_id->flags & EcsParent) { + term_id->flags |= EcsUp; + term_id->trav = EcsChildOf; + } + + if ((term_id->flags & EcsCascade) && !(term_id->flags & (EcsUp|EcsDown))) { + term_id->flags |= EcsUp; + } + + if ((term_id->flags & (EcsUp|EcsDown)) && !term_id->trav) { + term_id->trav = EcsIsA; + } + + if (term_id->trav && !(term_id->flags & EcsTraverseFlags)) { + term_id->flags |= EcsUp; + } + + return 0; +} + +static +int flecs_term_id_lookup( + const ecs_world_t *world, + ecs_entity_t scope, + ecs_term_id_t *term_id, + bool free_name, + ecs_filter_finalize_ctx_t *ctx) +{ + const char *name = term_id->name; + if (!name) { + return 0; + } + + if (term_id->flags & EcsIsVariable) { + if (!ecs_os_strcmp(name, "This") || !ecs_os_strcmp(name, "this")) { + term_id->id = EcsThis; + if (free_name) { + /* Safe, if free_name is true the filter owns the name */ + ecs_os_free(ECS_CONST_CAST(char*, term_id->name)); + } + term_id->name = NULL; + } + return 0; + } else if (term_id->flags & EcsIsName) { + return 0; + } + + ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + + if (ecs_identifier_is_0(name)) { + if (term_id->id) { + flecs_filter_error(ctx, "name '0' does not match entity id"); + return -1; + } + return 0; + } + + ecs_entity_t e = ecs_lookup_symbol(world, name, true, true); + if (scope && !e) { + e = ecs_lookup_child(world, scope, name); + } + + if (!e) { + if (ctx->filter && (ctx->filter->flags & EcsFilterUnresolvedByName)) { + term_id->flags |= EcsIsName; + term_id->flags &= ~EcsIsEntity; + } else { + flecs_filter_error(ctx, "unresolved identifier '%s'", name); + return -1; + } + } + + if (term_id->id && term_id->id != e) { + char *e_str = ecs_get_fullpath(world, term_id->id); + flecs_filter_error(ctx, "name '%s' does not match term.id '%s'", + name, e_str); + ecs_os_free(e_str); + return -1; + } + + term_id->id = e; + + if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || + !ecs_os_strcmp(name, "$")) + { + term_id->flags &= ~EcsIsEntity; + term_id->flags |= EcsIsVariable; + } + + /* Check if looked up id is alive (relevant for numerical ids) */ + if (!(term_id->flags & EcsIsName)) { + if (!ecs_is_alive(world, term_id->id)) { + flecs_filter_error(ctx, "identifier '%s' is not alive", term_id->name); + return -1; + } + + if (free_name) { + /* Safe, if free_name is true, the filter owns the name */ + ecs_os_free(ECS_CONST_CAST(char*, name)); + } + + term_id->name = NULL; + } + + return 0; +} + +static +int flecs_term_ids_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + ecs_term_id_t *src = &term->src; + ecs_term_id_t *first = &term->first; + ecs_term_id_t *second = &term->second; + + /* Include inherited components (like from prefabs) by default for src */ + if (!(src->flags & EcsTraverseFlags)) { + src->flags |= EcsSelf | EcsUp; + } + + /* Include subsets for component by default, to support inheritance */ + if (!(first->flags & EcsTraverseFlags)) { + first->flags |= EcsSelf; + if (first->id && first->flags & EcsIsEntity) { + if (flecs_id_record_get(world, ecs_pair(EcsIsA, first->id))) { + first->flags |= EcsDown; + } + } + } + + /* Traverse Self by default for pair target */ + if (!(second->flags & EcsTraverseFlags)) { + second->flags |= EcsSelf; + } + + /* Source defaults to This */ + if ((src->id == 0) && (src->name == NULL) && !(src->flags & EcsIsEntity)) { + src->id = EcsThis; + src->flags |= EcsIsVariable; + } + + /* Initialize term identifier flags */ + if (flecs_term_id_finalize_flags(src, ctx)) { + return -1; + } + if (flecs_term_id_finalize_flags(first, ctx)) { + return -1; + } + + if (flecs_term_id_finalize_flags(second, ctx)) { + return -1; + } + + /* Lookup term identifiers by name */ + if (flecs_term_id_lookup(world, 0, src, term->move, ctx)) { + return -1; + } + if (flecs_term_id_lookup(world, 0, first, term->move, ctx)) { + return -1; + } + + ecs_entity_t first_id = 0; + ecs_entity_t oneof = 0; + if (first->flags & EcsIsEntity) { + first_id = first->id; + + /* If first element of pair has OneOf property, lookup second element of + * pair in the value of the OneOf property */ + oneof = flecs_get_oneof(world, first_id); + } + + if (flecs_term_id_lookup(world, oneof, &term->second, term->move, ctx)) { + return -1; + } + + /* If source is 0, reset traversal flags */ + if (src->id == 0 && src->flags & EcsIsEntity) { + src->flags &= ~EcsTraverseFlags; + src->trav = 0; + } + /* If second is 0, reset traversal flags */ + if (second->id == 0 && second->flags & EcsIsEntity) { + second->flags &= ~EcsTraverseFlags; + second->trav = 0; + } + + /* If source is wildcard, term won't return any data */ + if ((src->flags & EcsIsVariable) && ecs_id_is_wildcard(src->id)) { + term->inout |= EcsInOutNone; + } + + return 0; +} + +static +ecs_entity_t flecs_term_id_get_entity( + const ecs_term_id_t *term_id) +{ + if (term_id->flags & EcsIsEntity) { + return term_id->id; /* Id is known */ + } else if (term_id->flags & EcsIsVariable) { + /* Return wildcard for variables, as they aren't known yet */ + if (term_id->id != EcsAny) { + /* Any variable should not use wildcard, as this would return all + * ids matching a wildcard, whereas Any returns the first match */ + return EcsWildcard; + } else { + return EcsAny; + } + } else { + return 0; /* Term id is uninitialized */ + } +} + +static +int flecs_term_populate_id( + ecs_term_t *term) +{ + ecs_entity_t first = flecs_term_id_get_entity(&term->first); + ecs_entity_t second = flecs_term_id_get_entity(&term->second); + ecs_id_t role = term->id_flags; + + if (first & ECS_ID_FLAGS_MASK) { + return -1; + } + if (second & ECS_ID_FLAGS_MASK) { + return -1; + } + + if ((second || term->second.flags == EcsIsEntity)) { + role = term->id_flags |= ECS_PAIR; + } + + if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { + term->id = first | role; + } else { + term->id = ecs_pair(first, second) | role; + } + + return 0; +} + +static +int flecs_term_populate_from_id( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + ecs_entity_t first = 0; + ecs_entity_t second = 0; + ecs_id_t role = term->id & ECS_ID_FLAGS_MASK; + + if (!role && term->id_flags) { + role = term->id_flags; + term->id |= role; + } + + if (term->id_flags && term->id_flags != role) { + flecs_filter_error(ctx, "mismatch between term.id & term.id_flags"); + return -1; + } + + term->id_flags = role; + + if (ECS_HAS_ID_FLAG(term->id, PAIR)) { + first = ECS_PAIR_FIRST(term->id); + second = ECS_PAIR_SECOND(term->id); + + if (!first) { + flecs_filter_error(ctx, "missing first element in term.id"); + return -1; + } + if (!second) { + if (first != EcsChildOf) { + flecs_filter_error(ctx, "missing second element in term.id"); + return -1; + } else { + /* (ChildOf, 0) is allowed so filter can be used to efficiently + * query for root entities */ + } + } + } else { + first = term->id & ECS_COMPONENT_MASK; + if (!first) { + flecs_filter_error(ctx, "missing first element in term.id"); + return -1; + } + } + + ecs_entity_t term_first = flecs_term_id_get_entity(&term->first); + if (term_first) { + if ((uint32_t)term_first != (uint32_t)first) { + flecs_filter_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + } else { + if (!(term->first.id = ecs_get_alive(world, first))) { + term->first.id = first; + } + } + + ecs_entity_t term_second = flecs_term_id_get_entity(&term->second); + if (term_second) { + if ((uint32_t)term_second != second) { + flecs_filter_error(ctx, "mismatch between term.id and term.second"); + return -1; + } + } else if (second) { + if (!(term->second.id = ecs_get_alive(world, second))) { + term->second.id = second; + } + } + + return 0; +} + +static +int flecs_term_verify_eq_pred( + const ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + ecs_entity_t first_id = term->first.id; + const ecs_term_id_t *second = &term->second; + const ecs_term_id_t *src = &term->src; + + if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { + flecs_filter_error(ctx, "invalid operator combination"); + goto error; + } + + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "both sides of operator cannot be a name"); + goto error; + } + + if ((src->flags & EcsIsEntity) && (second->flags & EcsIsEntity)) { + flecs_filter_error(ctx, "both sides of operator cannot be an entity"); + goto error; + } + + if (!(src->flags & EcsIsVariable)) { + flecs_filter_error(ctx, "left-hand of operator must be a variable"); + goto error; + } + + if (first_id == EcsPredMatch && !(second->flags & EcsIsName)) { + flecs_filter_error(ctx, "right-hand of match operator must be a string"); + goto error; + } + + if ((src->flags & EcsIsVariable) && (second->flags & EcsIsVariable)) { + if (src->id && src->id == second->id) { + flecs_filter_error(ctx, "both sides of operator are equal"); + goto error; + } + if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { + flecs_filter_error(ctx, "both sides of operator are equal"); + goto error; + } + } + + return 0; +error: + return -1; +} + +static +int flecs_term_verify( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + const ecs_term_id_t *first = &term->first; + const ecs_term_id_t *second = &term->second; + const ecs_term_id_t *src = &term->src; + ecs_entity_t first_id = 0, second_id = 0; + ecs_id_t role = term->id_flags; + ecs_id_t id = term->id; + + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; + } + + if (first->flags & EcsIsEntity) { + first_id = first->id; + } + if (second->flags & EcsIsEntity) { + second_id = second->id; + } + + if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { + return flecs_term_verify_eq_pred(term, ctx); + } + + if (role != (id & ECS_ID_FLAGS_MASK)) { + flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; + } + + if (ecs_term_id_is_set(second) && !ECS_HAS_ID_FLAG(role, PAIR)) { + flecs_filter_error(ctx, "expected PAIR flag for term with pair"); + return -1; + } else if (!ecs_term_id_is_set(second) && ECS_HAS_ID_FLAG(role, PAIR)) { + if (first_id != EcsChildOf) { + flecs_filter_error(ctx, "unexpected PAIR flag for term without pair"); + return -1; + } else { + /* Exception is made for ChildOf so we can use (ChildOf, 0) to match + * all entities in the root */ + } + } + + if (!ecs_term_id_is_set(src)) { + flecs_filter_error(ctx, "term.src is not initialized"); + return -1; + } + + if (!ecs_term_id_is_set(first)) { + flecs_filter_error(ctx, "term.first is not initialized"); + return -1; + } + + if (ECS_HAS_ID_FLAG(role, PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + flecs_filter_error(ctx, "invalid 0 for first element in pair id"); + return -1; + } + if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { + flecs_filter_error(ctx, "invalid 0 for second element in pair id"); + return -1; + } + + if ((first->flags & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) + { + flecs_filter_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->flags & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_filter_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + + if ((second->flags & EcsIsEntity) && + (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) + { + flecs_filter_error(ctx, "mismatch between term.id and term.second"); + return -1; + } + if ((second->flags & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_filter_error(ctx, + "expected wildcard for variable term.second (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + } else { + ecs_entity_t component = id & ECS_COMPONENT_MASK; + if (!component) { + flecs_filter_error(ctx, "missing component id"); + return -1; + } + if ((first->flags & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) + { + flecs_filter_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->flags & EcsIsVariable) && !ecs_id_is_wildcard(component)) { + char *id_str = ecs_id_str(world, id); + flecs_filter_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + } + + if (first_id) { + if (ecs_term_id_is_set(second)) { + ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; + if ((src->flags & mask) == (second->flags & mask)) { + bool is_same = false; + if (src->flags & EcsIsEntity) { + is_same = src->id == second->id; + } else if (src->name && second->name) { + is_same = !ecs_os_strcmp(src->name, second->name); + } + + if (is_same && ecs_has_id(world, first_id, EcsAcyclic) + && !(term->flags & EcsTermReflexive)) + { + char *pred_str = ecs_get_fullpath(world, term->first.id); + flecs_filter_error(ctx, "term with acyclic relationship" + " '%s' cannot have same subject and object", + pred_str); + ecs_os_free(pred_str); + return -1; + } + } + } + + if (second_id && !ecs_id_is_wildcard(second_id)) { + ecs_entity_t oneof = flecs_get_oneof(world, first_id); + if (oneof) { + if (!ecs_has_pair(world, second_id, EcsChildOf, oneof)) { + char *second_str = ecs_get_fullpath(world, second_id); + char *oneof_str = ecs_get_fullpath(world, oneof); + char *id_str = ecs_id_str(world, term->id); + flecs_filter_error(ctx, + "invalid target '%s' for %s: must be child of '%s'", + second_str, id_str, oneof_str); + ecs_os_free(second_str); + ecs_os_free(oneof_str); + ecs_os_free(id_str); + return -1; + } + } + } + } + + if (term->src.trav) { + if (!ecs_has_id(world, term->src.trav, EcsTraversable)) { + char *r_str = ecs_get_fullpath(world, term->src.trav); + flecs_filter_error(ctx, + "cannot traverse non-traversable relationship '%s'", r_str); + ecs_os_free(r_str); + return -1; + } + } + + return 0; +} + +static +int flecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) +{ + ctx->term = term; + + ecs_term_id_t *src = &term->src; + ecs_term_id_t *first = &term->first; + ecs_term_id_t *second = &term->second; + ecs_flags32_t first_flags = first->flags; + ecs_flags32_t src_flags = src->flags; + ecs_flags32_t second_flags = second->flags; + + if (term->id) { + if (flecs_term_populate_from_id(world, term, ctx)) { + return -1; + } + } + + if (flecs_term_ids_finalize(world, term, ctx)) { + return -1; + } + + if ((first->flags & EcsIsVariable) && (term->first.id == EcsAny)) { + term->flags |= EcsTermMatchAny; + } + if ((second->flags & EcsIsVariable) && (term->second.id == EcsAny)) { + term->flags |= EcsTermMatchAny; + } + if ((src->flags & EcsIsVariable) && (term->src.id == EcsAny)) { + term->flags |= EcsTermMatchAnySrc; + } + + /* If EcsVariable is used by itself, assign to predicate (singleton) */ + if ((src->id == EcsVariable) && (src->flags & EcsIsVariable)) { + src->id = first->id; + src->flags &= ~(EcsIsVariable | EcsIsEntity); + src->flags |= first->flags & (EcsIsVariable | EcsIsEntity); + } + if ((second->id == EcsVariable) && (second->flags & EcsIsVariable)) { + second->id = first->id; + second->flags &= ~(EcsIsVariable | EcsIsEntity); + second->flags |= first->flags & (EcsIsVariable | EcsIsEntity); + } + + ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; + if ((src->flags & mask) == (second->flags & mask)) { + bool is_same = false; + if (src->flags & EcsIsEntity) { + is_same = src->id == second->id; + } else if (src->name && second->name) { + is_same = !ecs_os_strcmp(src->name, second->name); + } + if (is_same) { + term->flags |= EcsTermSrcSecondEq; + } + } + if ((src->flags & mask) == (first->flags & mask)) { + bool is_same = false; + if (src->flags & EcsIsEntity) { + is_same = src->id == first->id; + } else if (src->name && first->name) { + is_same = !ecs_os_strcmp(src->name, first->name); + } + if (is_same) { + term->flags |= EcsTermSrcFirstEq; + } + } + + if (!term->id) { + if (flecs_term_populate_id(term)) { + return -1; + } + } + + /* If term queries for !(ChildOf, _), translate it to the builtin + * (ChildOf, 0) index which is a cheaper way to find root entities */ + if (term->oper == EcsNot && term->id == ecs_pair(EcsChildOf, EcsAny)) { + /* Only if the source is not EcsAny */ + if (!(term->src.id == EcsAny && (term->src.flags & EcsIsVariable))) { + term->oper = EcsAnd; + term->id = ecs_pair(EcsChildOf, 0); + second->id = 0; + second->flags |= EcsIsEntity; + second->flags &= ~EcsIsVariable; + } + } + + ecs_entity_t first_id = 0; + if (first->flags & EcsIsEntity) { + first_id = first->id; + } + + term->idr = flecs_query_id_record_get(world, term->id); + ecs_flags32_t id_flags = term->idr ? term->idr->flags : 0; + + if (first_id) { + ecs_entity_t first_trav = first->trav; + + /* If component is inherited from, set correct traversal flags */ + ecs_flags32_t first_trav_flags = first_flags & EcsTraverseFlags; + if (!first_trav && first_trav_flags != EcsSelf) { + /* Inheritance uses IsA by default, but can use any relationship */ + first_trav = EcsIsA; + } + + ecs_record_t *trav_record = NULL; + ecs_table_t *trav_table = NULL; + if (first_trav) { + trav_record = flecs_entities_get(world, first_trav); + trav_table = trav_record ? trav_record->table : NULL; + if (first_trav != EcsIsA) { + if (!trav_table || !ecs_table_has_id(world, trav_table, EcsTraversable)) { + flecs_filter_error(ctx, "first.trav is not traversable"); + return -1; + } + } + } + + /* Only enable inheritance for ids which are inherited from at the time + * of filter creation. To force component inheritance to be evaluated, + * an application can explicitly set traversal flags. */ + if ((first_trav_flags & EcsDown) || + flecs_id_record_get(world, ecs_pair(first_trav, first->id))) + { + if (first_trav_flags == EcsSelf) { + flecs_filter_error(ctx, "first.trav specified with self"); + return -1; + } + + if (!first_trav_flags || (first_trav_flags & EcsDown)) { + term->flags |= EcsTermIdInherited; + first->trav = first_trav; + if (!first_trav_flags) { + first->flags &= ~EcsTraverseFlags; + first->flags |= EcsDown; + ecs_assert(trav_table != NULL, ECS_INTERNAL_ERROR, NULL); + if ((first_trav == EcsIsA) || ecs_table_has_id( + world, trav_table, EcsReflexive)) + { + first->flags |= EcsSelf; + } + } + } + } + + /* Don't traverse ids that cannot be inherited */ + if ((id_flags & EcsIdDontInherit) && (src->trav == EcsIsA)) { + if (src_flags & (EcsUp | EcsDown)) { + flecs_filter_error(ctx, + "traversing not allowed for id that can't be inherited"); + return -1; + } + src->flags &= ~(EcsUp | EcsDown); + src->trav = 0; + } + + /* If component id is final, don't attempt component inheritance */ + ecs_record_t *first_record = flecs_entities_get(world, first_id); + ecs_table_t *first_table = first_record ? first_record->table : NULL; + if (first_table) { + if (ecs_table_has_id(world, first_table, EcsFinal)) { + if (first_flags & EcsDown) { + flecs_filter_error(ctx, "final id cannot be traversed down"); + return -1; + } + } + + /* Add traversal flags for transitive relationships */ + if (!(second_flags & EcsTraverseFlags) && ecs_term_id_is_set(second)) { + if (!((src->flags & EcsIsVariable) && (src->id == EcsAny))) { + if (!((second->flags & EcsIsVariable) && (second->id == EcsAny))) { + if (ecs_table_has_id(world, first_table, EcsTransitive)) { + second->flags |= EcsSelf|EcsUp|EcsTraverseAll; + second->trav = first_id; + term->flags |= EcsTermTransitive; + } + } + } + } + + if (ecs_table_has_id(world, first_table, EcsReflexive)) { + term->flags |= EcsTermReflexive; + } + } + } + + if (first->id == EcsVariable) { + flecs_filter_error(ctx, "invalid $ for term.first"); + return -1; + } + + if (term->id_flags & ECS_AND) { + term->oper = EcsAndFrom; + term->id &= ECS_COMPONENT_MASK; + term->id_flags = 0; + } + + if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { + if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { + flecs_filter_error(ctx, + "invalid inout value for AndFrom/OrFrom/NotFrom term"); + return -1; + } + } + + /* Is term trivial */ + bool trivial_term = true; + if (term->oper != EcsAnd) { + trivial_term = false; + } + if (ecs_id_is_wildcard(term->id)) { + if (!(term->idr && term->idr->flags & EcsIdExclusive)) { + trivial_term = false; + } + if (first->flags & EcsIsVariable) { + if (!ecs_id_is_wildcard(first->id) || first->id == EcsAny) { + trivial_term = false; + } + } + if (second->flags & EcsIsVariable) { + if (!ecs_id_is_wildcard(second->id) || second->id == EcsAny) { + trivial_term = false; + } + } + } + if (!ecs_term_match_this(term)) { + trivial_term = false; + } + if (term->flags & EcsTermIdInherited) { + trivial_term = false; + } + if (src->trav && src->trav != EcsIsA) { + trivial_term = false; + } + if (first->trav && first->trav != EcsIsA) { + trivial_term = false; + } + if (second->trav && second->trav != EcsIsA) { + trivial_term = false; + } + if (!(src->flags & EcsSelf)) { + trivial_term = false; + } + if (((term->first.id == EcsPredEq) || (term->first.id == EcsPredMatch)) && + (term->first.flags & EcsIsEntity)) + { + trivial_term = false; + } + + if (trivial_term) { + ECS_BIT_SET(term->flags, EcsTermIsTrivial); + } + + if (flecs_term_verify(world, term, ctx)) { + return -1; + } + + return 0; +} + +ecs_id_t flecs_to_public_id( + ecs_id_t id) +{ + if (ECS_PAIR_FIRST(id) == EcsUnion) { + return ecs_pair(ECS_PAIR_SECOND(id), EcsWildcard); + } else { + return id; + } +} + +ecs_id_t flecs_from_public_id( + ecs_world_t *world, + ecs_id_t id) +{ + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_id_record_t *idr = flecs_id_record_ensure(world, + ecs_pair(first, EcsWildcard)); + if (idr->flags & EcsIdUnion) { + return ecs_pair(EcsUnion, first); + } + } + + return id; +} + +bool ecs_identifier_is_0( + const char *id) +{ + return id[0] == '0' && !id[1]; +} + +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern) +{ + if (id == pattern) { + return true; + } + + if (ECS_HAS_ID_FLAG(pattern, PAIR)) { + if (!ECS_HAS_ID_FLAG(id, PAIR)) { + return false; + } + + ecs_entity_t id_rel = ECS_PAIR_FIRST(id); + ecs_entity_t id_obj = ECS_PAIR_SECOND(id); + ecs_entity_t pattern_rel = ECS_PAIR_FIRST(pattern); + ecs_entity_t pattern_obj = ECS_PAIR_SECOND(pattern); + + ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_check(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); + + if (pattern_rel == EcsWildcard) { + if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { + return true; + } + } else if (pattern_rel == EcsFlag) { + /* Used for internals, helps to keep track of which ids are used in + * pairs that have additional flags (like OVERRIDE and TOGGLE) */ + if (ECS_HAS_ID_FLAG(id, PAIR) && !ECS_IS_PAIR(id)) { + if (ECS_PAIR_FIRST(id) == pattern_obj) { + return true; + } + if (ECS_PAIR_SECOND(id) == pattern_obj) { + return true; + } + } + } else if (pattern_obj == EcsWildcard) { + if (pattern_rel == id_rel) { + return true; + } + } + } else { + if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { + return false; + } + + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { + return true; + } + } + +error: + return false; +} + +bool ecs_id_is_pair( + ecs_id_t id) +{ + return ECS_HAS_ID_FLAG(id, PAIR); +} + +bool ecs_id_is_wildcard( + ecs_id_t id) +{ + if ((id == EcsWildcard) || (id == EcsAny)) { + return true; + } + + bool is_pair = ECS_IS_PAIR(id); + if (!is_pair) { + return false; + } + + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); + + return (first == EcsWildcard) || (second == EcsWildcard) || + (first == EcsAny) || (second == EcsAny); +} + +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t id) +{ + if (!id) { + return false; + } + if (ecs_id_is_wildcard(id)) { + return false; + } + + if (ECS_HAS_ID_FLAG(id, PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + return false; + } + if (!ECS_PAIR_SECOND(id)) { + return false; + } + } else if (id & ECS_ID_FLAGS_MASK) { + if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { + return false; + } + } + + return true; +} + +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + return idr->flags; + } else { + return 0; + } +} + +bool ecs_term_id_is_set( + const ecs_term_id_t *id) +{ + return id->id != 0 || id->name != NULL || id->flags & EcsIsEntity; +} + +bool ecs_term_is_initialized( + const ecs_term_t *term) +{ + return term->id != 0 || ecs_term_id_is_set(&term->first); +} + +bool ecs_term_match_this( + const ecs_term_t *term) +{ + return (term->src.flags & EcsIsVariable) && (term->src.id == EcsThis); +} + +bool ecs_term_match_0( + const ecs_term_t *term) +{ + return (term->src.id == 0) && (term->src.flags & EcsIsEntity); +} + +int ecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term) +{ + ecs_filter_finalize_ctx_t ctx = {0}; + ctx.world = world; + ctx.term = term; + return flecs_term_finalize(world, term, &ctx); +} + +ecs_term_t ecs_term_copy( + const ecs_term_t *src) +{ + ecs_term_t dst = *src; + dst.name = ecs_os_strdup(src->name); + dst.first.name = ecs_os_strdup(src->first.name); + dst.src.name = ecs_os_strdup(src->src.name); + dst.second.name = ecs_os_strdup(src->second.name); + return dst; +} + +ecs_term_t ecs_term_move( + ecs_term_t *src) +{ + if (src->move) { + ecs_term_t dst = *src; + src->name = NULL; + src->first.name = NULL; + src->src.name = NULL; + src->second.name = NULL; + dst.move = false; + return dst; + } else { + ecs_term_t dst = ecs_term_copy(src); + dst.move = false; + return dst; + } +} + +void ecs_term_fini( + ecs_term_t *term) +{ + /* Safe, values are owned by term */ + ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); + ecs_os_free(ECS_CONST_CAST(char*, term->src.name)); + ecs_os_free(ECS_CONST_CAST(char*, term->second.name)); + ecs_os_free(term->name); + + term->first.name = NULL; + term->src.name = NULL; + term->second.name = NULL; + term->name = NULL; +} + +static +ecs_term_t* flecs_filter_or_other_type( + ecs_filter_t *f, + int32_t t) +{ + ecs_term_t *term = &f->terms[t]; + ecs_term_t *first = NULL; + while (t--) { + if (f->terms[t].oper != EcsOr) { + break; + } + first = &f->terms[t]; + } + + if (first) { + ecs_world_t *world = f->world; + const ecs_type_info_t *first_type; + if (first->idr) { + first_type = first->idr->type_info; + } else { + first_type = ecs_get_type_info(world, first->id); + } + const ecs_type_info_t *term_type; + if (term->idr) { + term_type = term->idr->type_info; + } else { + term_type = ecs_get_type_info(world, term->id); + } + + if (first_type == term_type) { + return NULL; + } + return first; + } else { + return NULL; + } +} + +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *f) +{ + int32_t i, term_count = f->term_count, field_count = 0; + ecs_term_t *terms = f->terms; + int32_t filter_terms = 0, scope_nesting = 0; + bool cond_set = false; + + ecs_filter_finalize_ctx_t ctx = {0}; + ctx.world = world; + ctx.filter = f; + + f->flags |= EcsFilterMatchOnlyThis; + + for (i = 0; i < term_count; i ++) { + bool filter_term = false; + ecs_term_t *term = &terms[i]; + ctx.term_index = i; + if (flecs_term_finalize(world, term, &ctx)) { + return -1; + } + + if (i && term[-1].oper == EcsOr) { + if (term[-1].src.id != term->src.id) { + flecs_filter_error(&ctx, "mismatching src.id for OR terms"); + return -1; + } + if (term->oper != EcsOr && term->oper != EcsAnd) { + flecs_filter_error(&ctx, + "term after OR operator must use AND operator"); + return -1; + } + } else { + field_count ++; + } + + term->field_index = field_count - 1; + + if (ecs_id_is_wildcard(term->id)) { + f->flags |= EcsFilterHasWildcards; + } + + if (ecs_term_match_this(term)) { + ECS_BIT_SET(f->flags, EcsFilterMatchThis); + } else { + ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); + } + + if (term->id == EcsPrefab) { + ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); + } + if (term->id == EcsDisabled && (term->src.flags & EcsSelf)) { + ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); + } + + if (ECS_BIT_IS_SET(f->flags, EcsFilterNoData)) { + term->inout = EcsInOutNone; + } + + if (term->oper == EcsNot && term->inout == EcsInOutDefault) { + term->inout = EcsInOutNone; + } + + if ((term->id == EcsWildcard) || (term->id == + ecs_pair(EcsWildcard, EcsWildcard))) + { + /* If term type is unknown beforehand, default the inout type to + * none. This prevents accidentally requesting lots of components, + * which can put stress on serializer code. */ + if (term->inout == EcsInOutDefault) { + term->inout = EcsInOutNone; + } + } + + if (term->inout == EcsInOutNone) { + filter_term = true; + } else if (term->idr) { + if (!term->idr->type_info && !(term->idr->flags & EcsIdUnion)) { + filter_term = true; + } + } else if (!ecs_id_is_union(world, term->id)) { + /* Union ids aren't filters because they return their target + * as component value with type ecs_entity_t */ + if (ecs_id_is_tag(world, term->id)) { + filter_term = true; + } else if (ECS_PAIR_SECOND(term->id) == EcsWildcard) { + /* If the second element of a pair is a wildcard and the first + * element is not a type, we can't know in advance what the + * type of the term is, so it can't provide data. */ + if (!ecs_get_type_info(world, ecs_pair_first(world, term->id))) { + filter_term = true; + } + } + } + + if (!filter_term) { + if (term->oper == EcsOr || (i && term[-1].oper == EcsOr)) { + ecs_term_t *first = flecs_filter_or_other_type(f, i); + if (first) { + if (first == &term[-1]) { + filter_terms ++; + } + filter_term = true; + } + } + } + + if (filter_term) { + filter_terms ++; + term->flags |= EcsTermNoData; + } else { + f->data_fields |= (1llu << term->field_index); + } + + if (term->oper != EcsNot || !ecs_term_match_this(term)) { + ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); + } + + if (term->idr) { + if (ecs_os_has_threading()) { + ecs_os_ainc(&term->idr->keep_alive); + } else { + term->idr->keep_alive ++; + } + } + + if (term->oper == EcsOptional || term->oper == EcsNot) { + cond_set = true; + } + + if (term->first.id == EcsPredEq || term->first.id == EcsPredMatch || + term->first.id == EcsPredLookup) + { + f->flags |= EcsFilterHasPred; + } + + if (term->first.id == EcsScopeOpen) { + f->flags |= EcsFilterHasScopes; + scope_nesting ++; + } + + if (term->first.id == EcsScopeClose) { + if (i && terms[i - 1].first.id == EcsScopeOpen) { + flecs_filter_error(&ctx, "invalid empty scope"); + return -1; + } + + f->flags |= EcsFilterHasScopes; + scope_nesting --; + } + if (scope_nesting < 0) { + flecs_filter_error(&ctx, "'}' without matching '{'"); + } + } + + if (scope_nesting != 0) { + flecs_filter_error(&ctx, "missing '}'"); + return -1; + } + + if (term_count && (terms[term_count - 1].oper == EcsOr)) { + flecs_filter_error(&ctx, "last term of filter can't have OR operator"); + return -1; + } + + f->field_count = flecs_ito(int8_t, field_count); + + if (field_count) { + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_id_record_t *idr = term->idr; + int32_t field = term->field_index; + f->ids[field] = term->id; + + if (term->oper == EcsOr || (i && (term[-1].oper == EcsOr))) { + if (flecs_filter_or_other_type(f, i)) { + f->sizes[field] = 0; + f->ids[field] = 0; + continue; + } + } + + if (idr) { + if (!ECS_IS_PAIR(idr->id) || ECS_PAIR_FIRST(idr->id) != EcsWildcard) { + if (idr->flags & EcsIdUnion) { + f->sizes[field] = ECS_SIZEOF(ecs_entity_t); +#ifdef FLECS_META + f->ids[field] = ecs_id(ecs_entity_t); +#endif + } else if (idr->type_info) { + f->sizes[field] = idr->type_info->size; + f->ids[field] = idr->id; + } + } + } else { + bool is_union = false; + if (ECS_IS_PAIR(term->id)) { + ecs_entity_t first = ecs_pair_first(world, term->id); + if (ecs_has_id(world, first, EcsUnion)) { + is_union = true; + } + } + if (is_union) { + f->sizes[field] = ECS_SIZEOF(ecs_entity_t); +#ifdef FLECS_META + f->ids[field] = ecs_id(ecs_entity_t); +#endif + } else { + const ecs_type_info_t *ti = ecs_get_type_info( + world, term->id); + if (ti) { + f->sizes[field] = ti->size; + f->ids[field] = term->id; + } + } + } + } + } else { + f->sizes = NULL; + f->ids = NULL; + } + + ecs_assert(filter_terms <= term_count, ECS_INTERNAL_ERROR, NULL); + if (filter_terms == term_count) { + ECS_BIT_SET(f->flags, EcsFilterNoData); + } + + ECS_BIT_COND(f->flags, EcsFilterHasCondSet, cond_set); + + /* Check if this is a trivial filter */ + if ((f->flags & EcsFilterMatchOnlyThis)) { + if (!(f->flags & + (EcsFilterHasPred|EcsFilterMatchDisabled|EcsFilterMatchPrefab))) + { + ECS_BIT_SET(f->flags, EcsFilterMatchOnlySelf); + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *src = &term->src; + + if (src->flags & EcsUp) { + ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlySelf); + } + + if (!(term->flags & EcsTermIsTrivial)) { + break; + } + + if (!(f->flags & EcsFilterNoData)) { + if (term->inout == EcsInOutNone) { + break; + } + } + } + if (term_count && (i == term_count)) { + ECS_BIT_SET(f->flags, EcsFilterIsTrivial); + } + } + } + + return 0; +} + +/* Implementation for iterable mixin */ +static +void flecs_filter_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) +{ + ecs_poly_assert(poly, ecs_filter_t); + + if (filter) { + iter[1] = ecs_filter_iter(world, ECS_CONST_CAST(ecs_filter_t*, poly)); + iter[0] = ecs_term_chain_iter(&iter[1], filter); + } else { + iter[0] = ecs_filter_iter(world, ECS_CONST_CAST(ecs_filter_t*, poly)); + } +} + +/* Implementation for dtor mixin */ +static +void flecs_filter_fini( + ecs_filter_t *filter) +{ + if (filter->terms) { + int i, count = filter->term_count; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &filter->terms[i]; + if (term->idr) { + if (!(filter->world->flags & EcsWorldQuit)) { + if (ecs_os_has_threading()) { + ecs_os_adec(&term->idr->keep_alive); + } else { + term->idr->keep_alive --; + } + } + } + ecs_term_fini(&filter->terms[i]); + } + + if (filter->flags & EcsFilterOwnsTermsStorage) { + /* Memory allocated for both terms & sizes */ + ecs_os_free(filter->terms); + } else { + ecs_os_free(filter->sizes); + ecs_os_free(filter->ids); + } + } + + filter->terms = NULL; + + if (filter->flags & EcsFilterOwnsStorage) { + ecs_os_free(filter); + } +} + +void ecs_filter_fini( + ecs_filter_t *filter) +{ + if ((filter->flags & EcsFilterOwnsStorage) && filter->entity) { + /* If filter is associated with entity, use poly dtor path */ + ecs_delete(filter->world, filter->entity); + } else { + flecs_filter_fini(filter); + } +} + +ecs_filter_t* ecs_filter_init( + ecs_world_t *world, + const ecs_filter_desc_t *desc) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + + ecs_filter_t *f = desc->storage; + int32_t i, term_count = desc->terms_buffer_count, storage_count = 0, expr_count = 0; + const ecs_term_t *terms = desc->terms_buffer; + ecs_term_t *storage_terms = NULL, *expr_terms = NULL; + + if (f) { + ecs_check(f->hdr.magic == ecs_filter_t_magic, + ECS_INVALID_PARAMETER, NULL); + storage_count = f->term_count; + storage_terms = f->terms; + ecs_poly_init(f, ecs_filter_t); + } else { + f = ecs_poly_new(ecs_filter_t); + f->flags |= EcsFilterOwnsStorage; + } + if (!storage_terms) { + f->flags |= EcsFilterOwnsTermsStorage; + } + + ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); + ECS_BIT_SET(f->flags, EcsFilterMatchAnything); + f->flags |= desc->flags; + f->world = world; + + /* If terms_buffer was not set, count number of initialized terms in + * static desc::terms array */ + if (!terms) { + ecs_check(term_count == 0, ECS_INVALID_PARAMETER, NULL); + terms = desc->terms; + for (i = 0; i < FLECS_TERM_DESC_MAX; i ++) { + if (!ecs_term_is_initialized(&terms[i])) { + break; + } + term_count ++; + } + } else { + ecs_check(term_count != 0, ECS_INVALID_PARAMETER, NULL); + } + + /* If expr is set, parse query expression */ + const char *expr = desc->expr; + ecs_entity_t entity = desc->entity; + if (expr) { +#ifdef FLECS_PARSER + const char *name = NULL; + const char *ptr = desc->expr; + ecs_term_t term = {0}; + ecs_term_id_t extra_args[ECS_PARSER_MAX_ARGS]; + ecs_oper_kind_t extra_oper = 0; + int32_t expr_size = 0; + + ecs_os_zeromem(extra_args); + + if (entity) { + name = ecs_get_name(world, entity); + } + + while (ptr[0] && (ptr = ecs_parse_term( + world, name, expr, ptr, &term, &extra_oper, extra_args, true))) + { + if (!ecs_term_is_initialized(&term)) { + break; + } + + int32_t arg = 0; + + do { + ecs_assert(arg <= ECS_PARSER_MAX_ARGS, ECS_INTERNAL_ERROR, NULL); + + if (expr_count == expr_size) { + expr_size = expr_size ? expr_size * 2 : 8; + expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); + } + + ecs_term_t *expr_term = &expr_terms[expr_count ++]; + *expr_term = term; + + if (arg) { + if (extra_oper == EcsAnd) { + expr_term->src = expr_term[-1].second; + expr_term->second = extra_args[arg - 1]; + } else if (extra_oper == EcsOr) { + expr_term->src = expr_term[-1].src; + expr_term->second = extra_args[arg - 1]; + expr_term[-1].oper = EcsOr; + } + if (expr_term->first.name != NULL) { + expr_term->first.name = ecs_os_strdup( + expr_term->first.name); + } + if (expr_term->src.name != NULL) { + expr_term->src.name = ecs_os_strdup( + expr_term->src.name); + } + } + } while (ecs_term_id_is_set(&extra_args[arg ++])); + + if (ptr[0] == '\n') { + break; + } + + ecs_os_zeromem(extra_args); + } + + if (!ptr) { + /* Set terms in filter object to make sur they get cleaned up */ + f->terms = expr_terms; + f->term_count = flecs_ito(int8_t, expr_count); + f->flags |= EcsFilterOwnsTermsStorage; + goto error; + } +#else + (void)expr; + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* If storage is provided, make sure it's large enough */ + ecs_check(!storage_terms || storage_count >= (term_count + expr_count), + ECS_INVALID_PARAMETER, NULL); + + if (term_count || expr_count) { + /* Allocate storage for terms and sizes array */ + if (!storage_terms) { + ecs_assert(f->flags & EcsFilterOwnsTermsStorage, + ECS_INTERNAL_ERROR, NULL); + f->term_count = flecs_ito(int8_t, term_count + expr_count); + ecs_size_t terms_size = ECS_SIZEOF(ecs_term_t) * f->term_count; + ecs_size_t ids_size = ECS_SIZEOF(ecs_id_t) * f->term_count; + ecs_size_t sizes_size = ECS_SIZEOF(int32_t) * f->term_count; + f->terms = ecs_os_calloc(terms_size + sizes_size + ids_size); + f->ids = ECS_OFFSET(f->terms, terms_size); + f->sizes = ECS_OFFSET(f->terms, terms_size + ids_size); + } else { + f->terms = storage_terms; + f->term_count = flecs_ito(int8_t, storage_count); + f->sizes = ecs_os_calloc_n(ecs_size_t, term_count); + f->ids = ecs_os_calloc_n(ecs_id_t, term_count); + } + + /* Copy terms to filter storage */ + for (i = 0; i < term_count; i ++) { + f->terms[i] = ecs_term_copy(&terms[i]); + /* Allow freeing resources from expr parser during finalization */ + f->terms[i].move = true; + } + + /* Move expr terms to filter storage */ + for (i = 0; i < expr_count; i ++) { + f->terms[i + term_count] = ecs_term_move(&expr_terms[i]); + /* Allow freeing resources from expr parser during finalization */ + f->terms[i + term_count].move = true; + } + ecs_os_free(expr_terms); + } + + /* Ensure all fields are consistent and properly filled out */ + if (ecs_filter_finalize(world, f)) { + goto error; + } + + /* Any allocated resources remaining in terms are now owned by filter */ + for (i = 0; i < f->term_count; i ++) { + f->terms[i].move = false; + } + + f->variable_names[0] = NULL; + f->iterable.init = flecs_filter_iter_init; + f->dtor = (ecs_poly_dtor_t)flecs_filter_fini; + f->entity = entity; + + if (entity && (f->flags & EcsFilterOwnsStorage)) { + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_filter_t); + poly->poly = f; + ecs_poly_modified(world, entity, ecs_filter_t); + } + + return f; +error: + ecs_filter_fini(f); + return NULL; +} + +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src) +{ + if (src == dst) { + return; + } + + if (src) { + *dst = *src; + + int32_t i, term_count = src->term_count; + ecs_size_t terms_size = ECS_SIZEOF(ecs_term_t) * term_count; + ecs_size_t sizes_size = ECS_SIZEOF(int32_t) * term_count; + ecs_size_t ids_size = ECS_SIZEOF(ecs_id_t) * term_count; + dst->terms = ecs_os_malloc(terms_size + sizes_size + ids_size); + dst->ids = ECS_OFFSET(dst->terms, terms_size); + dst->sizes = ECS_OFFSET(dst->terms, terms_size + ids_size); + dst->flags |= EcsFilterOwnsTermsStorage; + ecs_os_memcpy_n(dst->sizes, src->sizes, int32_t, term_count); + ecs_os_memcpy_n(dst->ids, src->ids, int32_t, term_count); + + for (i = 0; i < term_count; i ++) { + dst->terms[i] = ecs_term_copy(&src->terms[i]); + } + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); + } +} + +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src) +{ + if (src == dst) { + return; + } + + if (src) { + *dst = *src; + if (src->flags & EcsFilterOwnsTermsStorage) { + dst->terms = src->terms; + dst->sizes = src->sizes; + dst->ids = src->ids; + dst->flags |= EcsFilterOwnsTermsStorage; + } else { + ecs_filter_copy(dst, src); + } + src->terms = NULL; + src->sizes = NULL; + src->ids = NULL; + src->term_count = 0; + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); + } +} + +static +void flecs_filter_str_add_id( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_term_id_t *id, + bool is_subject, + ecs_flags32_t default_traverse_flags) +{ + bool is_added = false; + if (!is_subject || id->id != EcsThis) { + if (id->flags & EcsIsVariable && !ecs_id_is_wildcard(id->id)) { + ecs_strbuf_appendlit(buf, "$"); + } + if (id->id) { + char *path = ecs_get_fullpath(world, id->id); + ecs_strbuf_appendstr(buf, path); + ecs_os_free(path); + } else if (id->name) { + ecs_strbuf_appendstr(buf, id->name); + } else { + ecs_strbuf_appendlit(buf, "0"); + } + is_added = true; + } + + ecs_flags32_t flags = id->flags; + if (!(flags & EcsTraverseFlags)) { + /* If flags haven't been set yet, initialize with defaults. This can + * happen if an error is thrown while the term is being finalized */ + flags |= default_traverse_flags; + } + + if ((flags & EcsTraverseFlags) != default_traverse_flags) { + if (is_added) { + ecs_strbuf_list_push(buf, ":", "|"); + } else { + ecs_strbuf_list_push(buf, "", "|"); + } + if (id->flags & EcsSelf) { + ecs_strbuf_list_appendstr(buf, "self"); + } + if (id->flags & EcsUp) { + ecs_strbuf_list_appendstr(buf, "up"); + } + if (id->flags & EcsDown) { + ecs_strbuf_list_appendstr(buf, "down"); + } + + if (id->trav && (id->trav != EcsIsA)) { + ecs_strbuf_list_push(buf, "(", ""); + + char *rel_path = ecs_get_fullpath(world, id->trav); + ecs_strbuf_appendstr(buf, rel_path); + ecs_os_free(rel_path); + + ecs_strbuf_list_pop(buf, ")"); + } + + ecs_strbuf_list_pop(buf, ""); + } +} + +static +void flecs_term_str_w_strbuf( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf, + int32_t t) +{ + const ecs_term_id_t *src = &term->src; + const ecs_term_id_t *second = &term->second; + + uint8_t def_src_mask = EcsSelf|EcsUp; + uint8_t def_first_mask = EcsSelf; + uint8_t def_second_mask = EcsSelf; + + bool pred_set = ecs_term_id_is_set(&term->first); + bool subj_set = !ecs_term_match_0(term); + bool obj_set = ecs_term_id_is_set(second); + + if (term->first.id == EcsScopeOpen) { + ecs_strbuf_appendlit(buf, "{"); + return; + } else if (term->first.id == EcsScopeClose) { + ecs_strbuf_appendlit(buf, "}"); + return; + } + + if (((term->first.id == EcsPredEq) || (term->first.id == EcsPredMatch)) && + (term->first.flags & EcsIsEntity)) + { + ecs_strbuf_appendlit(buf, "$"); + if (term->src.id == EcsThis && term->src.flags & EcsIsVariable) { + ecs_strbuf_appendlit(buf, "this"); + } else if (term->src.flags & EcsIsVariable) { + ecs_strbuf_appendstr(buf, term->src.name); + } else { + /* Shouldn't happen */ + } + + if (term->first.id == EcsPredEq) { + if (term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, " != "); + } else { + ecs_strbuf_appendlit(buf, " == "); + } + } else if (term->first.id == EcsPredMatch) { + ecs_strbuf_appendlit(buf, " ~= "); + } + + if (term->second.flags & EcsIsEntity) { + if (term->second.id != 0) { + ecs_get_path_w_sep_buf( + world, 0, term->second.id, ".", NULL, buf); + } + } else { + if (term->second.flags & EcsIsVariable) { + ecs_strbuf_appendlit(buf, "$"); + if (term->second.name) { + ecs_strbuf_appendstr(buf, term->second.name); + } else if (term->second.id == EcsThis) { + ecs_strbuf_appendlit(buf, "this"); + } + } else if (term->second.flags & EcsIsName) { + ecs_strbuf_appendlit(buf, "\""); + if (term->first.id == EcsPredMatch && term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, "!"); + } + ecs_strbuf_appendstr(buf, term->second.name); + ecs_strbuf_appendlit(buf, "\""); + } + } + + return; + } + + if (!t || !(term[-1].oper == EcsOr)) { + if (term->inout == EcsIn) { + ecs_strbuf_appendlit(buf, "[in] "); + } else if (term->inout == EcsInOut) { + ecs_strbuf_appendlit(buf, "[inout] "); + } else if (term->inout == EcsOut) { + ecs_strbuf_appendlit(buf, "[out] "); + } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { + ecs_strbuf_appendlit(buf, "[none] "); + } + } + + if (term->first.flags & EcsIsEntity && term->first.id != 0) { + if (ecs_has_id(world, term->first.id, EcsDontInherit)) { + def_src_mask = EcsSelf; + } + } + + if (term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendlit(buf, "?"); + } + + if (!subj_set) { + flecs_filter_str_add_id(world, buf, &term->first, false, + def_first_mask); + if (!obj_set) { + ecs_strbuf_appendlit(buf, "()"); + } else { + ecs_strbuf_appendlit(buf, "(0,"); + flecs_filter_str_add_id(world, buf, &term->second, false, + def_second_mask); + ecs_strbuf_appendlit(buf, ")"); + } + } else if (ecs_term_match_this(term) && + (src->flags & EcsTraverseFlags) == def_src_mask) + { + if (pred_set) { + if (obj_set) { + ecs_strbuf_appendlit(buf, "("); + } + flecs_filter_str_add_id(world, buf, &term->first, false, def_first_mask); + if (obj_set) { + ecs_strbuf_appendlit(buf, ","); + flecs_filter_str_add_id( + world, buf, &term->second, false, def_second_mask); + ecs_strbuf_appendlit(buf, ")"); + } + } else if (term->id) { + char *str = ecs_id_str(world, term->id); + ecs_strbuf_appendstr(buf, str); + ecs_os_free(str); + } + } else { + if (term->id_flags && !ECS_HAS_ID_FLAG(term->id_flags, PAIR)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(term->id_flags)); + ecs_strbuf_appendch(buf, '|'); + } + + flecs_filter_str_add_id(world, buf, &term->first, false, def_first_mask); + ecs_strbuf_appendlit(buf, "("); + if (term->src.flags & EcsIsEntity && term->src.id == term->first.id) { + ecs_strbuf_appendlit(buf, "$"); + } else { + flecs_filter_str_add_id(world, buf, &term->src, true, def_src_mask); + } + if (obj_set) { + ecs_strbuf_appendlit(buf, ","); + flecs_filter_str_add_id(world, buf, &term->second, false, def_second_mask); + } + ecs_strbuf_appendlit(buf, ")"); + } +} + +char* ecs_term_str( + const ecs_world_t *world, + const ecs_term_t *term) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + flecs_term_str_w_strbuf(world, term, &buf, 0); + return ecs_strbuf_get(&buf); +} + +static +char* flecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_filter_finalize_ctx_t *ctx, + int32_t *term_start_out) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + + if (term_start_out && ctx) { + if (ctx->term_index == i) { + term_start_out[0] = ecs_strbuf_written(&buf); + if (i) { + term_start_out[0] += 2; /* whitespace + , */ + } + } + } + + flecs_term_str_w_strbuf(world, term, &buf, i); + + if (i != (count - 1)) { + if (term->oper == EcsOr) { + ecs_strbuf_appendlit(&buf, " || "); + } else { + if (term->first.id != EcsScopeOpen) { + if (term[1].first.id != EcsScopeClose) { + ecs_strbuf_appendlit(&buf, ", "); + } + } + } + } + } + + return ecs_strbuf_get(&buf); +error: + return NULL; +} + +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + return flecs_filter_str(world, filter, NULL, NULL); +} + +int32_t ecs_filter_find_this_var( + const ecs_filter_t *filter) +{ + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { + /* Filters currently only support the This variable at index 0. Only + * return 0 if filter actually has terms for the This variable. */ + return 0; + } + +error: + return -1; +} + +/* Check if the id is a pair that has Any as first or second element. Any + * pairs behave just like Wildcard pairs and reuses the same data structures, + * with as only difference that the number of results returned for an Any pair + * is never more than one. This function is used to tell the difference. */ +static +bool is_any_pair( + ecs_id_t id) +{ + if (!ECS_HAS_ID_FLAG(id, PAIR)) { + return false; + } + + if (ECS_PAIR_FIRST(id) == EcsAny) { + return true; + } + if (ECS_PAIR_SECOND(id) == EcsAny) { + return true; + } + + return false; +} + +static +bool flecs_n_term_match_table( + ecs_world_t *world, + const ecs_term_t *term, + const ecs_table_t *table, + ecs_entity_t type_id, + ecs_oper_kind_t oper, + ecs_id_t *id_out, + int32_t *column_out, + ecs_entity_t *subject_out, + int32_t *match_index_out, + bool first, + ecs_flags32_t iter_flags) +{ + (void)column_out; + + const ecs_type_t *type = ecs_get_type(world, type_id); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t *ids = type->array; + int32_t i, count = type->count; + ecs_term_t temp = *term; + temp.oper = EcsAnd; + + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { + continue; + } + bool result; + if (ECS_HAS_ID_FLAG(id, AND)) { + ecs_oper_kind_t id_oper = EcsAndFrom; + result = flecs_n_term_match_table(world, term, table, + id & ECS_COMPONENT_MASK, id_oper, id_out, column_out, + subject_out, match_index_out, first, iter_flags); + } else { + temp.id = id; + result = flecs_term_match_table(world, &temp, table, id_out, + 0, subject_out, match_index_out, first, iter_flags); + } + if (!result && oper == EcsAndFrom) { + return false; + } else + if (result && oper == EcsOrFrom) { + return true; + } + } + + if (oper == EcsAndFrom) { + if (id_out) { + id_out[0] = type_id; + } + return true; + } else + if (oper == EcsOrFrom) { + return false; + } + + return false; +} + +bool flecs_term_match_table( + ecs_world_t *world, + const ecs_term_t *term, + const ecs_table_t *table, + ecs_id_t *id_out, + int32_t *column_out, + ecs_entity_t *subject_out, + int32_t *match_index_out, + bool first, + ecs_flags32_t iter_flags) +{ + const ecs_term_id_t *src = &term->src; + ecs_oper_kind_t oper = term->oper; + const ecs_table_t *match_table = table; + ecs_id_t id = term->id; + + ecs_entity_t src_id = src->id; + if (ecs_term_match_0(term)) { + if (id_out) { + id_out[0] = id; /* If no entity is matched, just set id */ + } + return true; + } + + if (oper == EcsAndFrom || oper == EcsOrFrom) { + return flecs_n_term_match_table(world, term, table, term->id, + term->oper, id_out, column_out, subject_out, match_index_out, first, + iter_flags); + } + + /* If source is not This, search in table of source */ + if (!ecs_term_match_this(term)) { + if (iter_flags & EcsIterEntityOptional) { + /* Treat entity terms as optional */ + oper = EcsOptional; + } + + if (ecs_is_alive(world, src_id)) { + match_table = ecs_get_table(world, src_id); + } else { + match_table = NULL; + } + + if (match_table) { + } else if (oper != EcsOptional) { + return false; + } + } else { + /* If filter contains This terms, a table must be provided */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (!match_table) { + return false; + } + + ecs_entity_t source = 0; + + /* If first = false, we're searching from an offset. This supports returning + * multiple results when using wildcard filters. */ + int32_t column = 0; + if (!first && column_out && column_out[0] != 0) { + column = column_out[0]; + if (column < 0) { + /* In case column is not from This, flip sign */ + column = -column; + } + + /* Remove base 1 offset */ + column --; + } + + /* Find location, source and id of match in table type */ + ecs_table_record_t *tr = 0; + bool is_any = is_any_pair(id); + + ecs_id_record_t *idr = term->idr; + if (world->flags & EcsWorldQuit) { + /* During world cleanup the keep_alive assert for id records is no + * longer enforced */ + idr = NULL; + } + column = flecs_search_relation_w_idr(world, match_table, + column, id, src->trav, src->flags, &source, id_out, &tr, idr); + + if (tr && match_index_out) { + if (!is_any) { + match_index_out[0] = tr->count; + } else { + match_index_out[0] = 1; + } + } + + bool result = column != -1; + + if (oper == EcsNot) { + if (match_index_out) { + match_index_out[0] = 1; + } + result = !result; + } + + if (oper == EcsOptional) { + result = true; + } + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if ((column == -1) && (src->flags & EcsUp) && (table->flags & EcsTableHasTarget)) { + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t rel = ECS_PAIR_SECOND(table->type.array[table->_->ft_offset]); + if (rel == (uint32_t)src->trav) { + result = true; + } + } + + if (!result) { + if (iter_flags & EcsFilterPopulate) { + column = 0; + } else { + return false; + } + } + + if (!ecs_term_match_this(term)) { + if (!source) { + source = src_id; + } + } + + if (id_out && column < 0) { + id_out[0] = id; + } + + if (column_out) { + if (column >= 0) { + column ++; + if (source != 0) { + column *= -1; + } + column_out[0] = column; + } else { + column_out[0] = 0; + } + } + + if (subject_out) { + subject_out[0] = source; + } + + return result; +} + +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_id_t *ids, + int32_t *columns, + ecs_entity_t *sources, + int32_t *match_indices, + int32_t *matches_left, + bool first, + int32_t skip_term, + ecs_flags32_t iter_flags) +{ + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + int32_t match_count = 1; + bool result = true; + + if (matches_left) { + match_count = *matches_left; + } + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_oper_kind_t oper = term->oper; + if (i == skip_term) { + if (oper != EcsAndFrom && oper != EcsOrFrom && oper != EcsNotFrom) { + continue; + } + } + + ecs_term_id_t *src = &term->src; + const ecs_table_t *match_table = table; + int32_t t_i = term->field_index; + + ecs_entity_t src_id = src->id; + if (!src_id) { + if (ids) { + ids[t_i] = term->id; + } + continue; + } + + if (!ecs_term_match_this(term)) { + if (ecs_is_alive(world, src_id)) { + match_table = ecs_get_table(world, src_id); + } else { + match_table = NULL; + } + } else { + if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) { + continue; + } + + /* If filter contains This terms, table must be provided */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + } + + int32_t match_index = 0; + if (!i || term[-1].oper != EcsOr) { + result = false; + } else { + if (result) { + continue; /* Already found matching OR term */ + } + } + + bool term_result = flecs_term_match_table(world, term, match_table, + ids ? &ids[t_i] : NULL, + columns ? &columns[t_i] : NULL, + sources ? &sources[t_i] : NULL, + &match_index, + first, + iter_flags); + + if (i && term[-1].oper == EcsOr) { + result |= term_result; + } else { + result = term_result; + } + + if (oper != EcsOr && !result) { + return false; + } + + if (first && match_index) { + match_count *= match_index; + } + if (match_indices) { + match_indices[t_i] = match_index; + } + } + + if (matches_left) { + *matches_left = match_count; + } + + return true; +} + +static +void term_iter_init_no_data( + ecs_term_iter_t *iter) +{ + iter->term = (ecs_term_t){ .field_index = -1 }; + iter->self_index = NULL; + iter->index = 0; +} + +static +void term_iter_init_w_idr( + const ecs_term_t *term, + ecs_term_iter_t *iter, + ecs_id_record_t *idr, + bool empty_tables) +{ + if (idr) { + if (empty_tables) { + flecs_table_cache_all_iter(&idr->cache, &iter->it); + } else { + flecs_table_cache_iter(&idr->cache, &iter->it); + } + } else { + term_iter_init_no_data(iter); + } + + iter->index = 0; + iter->empty_tables = empty_tables; + iter->size = 0; + if (term && term->idr && term->idr->type_info) { + iter->size = term->idr->type_info->size; + } +} + +static +void term_iter_init_wildcard( + const ecs_world_t *world, + ecs_term_iter_t *iter, + bool empty_tables) +{ + iter->term = (ecs_term_t){ .field_index = -1 }; + iter->self_index = flecs_id_record_get(world, EcsAny); + ecs_id_record_t *idr = iter->cur = iter->self_index; + term_iter_init_w_idr(NULL, iter, idr, empty_tables); +} + +static +void term_iter_init( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_iter_t *iter, + bool empty_tables) +{ + const ecs_term_id_t *src = &term->src; + + iter->term = *term; + + if (src->flags & EcsSelf) { + iter->self_index = term->idr; + if (!iter->self_index) { + iter->self_index = flecs_query_id_record_get(world, term->id); + } + } + + if (src->flags & EcsUp) { + iter->set_index = flecs_id_record_get(world, + ecs_pair(src->trav, EcsWildcard)); + } + + ecs_id_record_t *idr; + if (iter->self_index) { + idr = iter->cur = iter->self_index; + } else { + idr = iter->cur = iter->set_index; + } + + term_iter_init_w_idr(term, iter, idr, empty_tables); +} + +ecs_iter_t ecs_term_iter( + const ecs_world_t *stage, + ecs_term_t *term) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + flecs_process_pending_tables(world); + + if (ecs_term_finalize(world, term)) { + ecs_throw(ECS_INVALID_PARAMETER, NULL); + } + + ecs_iter_t it = { + .real_world = ECS_CONST_CAST(ecs_world_t*, world), + .world = ECS_CONST_CAST(ecs_world_t*, stage), + .field_count = 1, + .next = ecs_term_next + }; + + /* Term iter populates the iterator with arrays from its own cache, ensure + * they don't get overwritten by flecs_iter_validate. + * + * Note: the reason the term iterator doesn't use the iterator cache itself + * (which could easily accommodate a single term) is that the filter iterator + * is built on top of the term iterator. The private cache of the term + * iterator keeps the filter iterator code simple, as it doesn't need to + * worry about the term iter overwriting the iterator fields. */ + flecs_iter_init(stage, &it, 0); + term_iter_init(world, term, &it.priv.iter.term, false); + ECS_BIT_COND(it.flags, EcsIterNoData, it.priv.iter.term.size == 0); + + return it; +error: + return (ecs_iter_t){ 0 }; +} + +ecs_iter_t ecs_term_chain_iter( + const ecs_iter_t *chain_it, + ecs_term_t *term) +{ + ecs_check(chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_world_t *world = chain_it->real_world; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ecs_term_finalize(world, term)) { + ecs_throw(ECS_INVALID_PARAMETER, NULL); + } + + ecs_iter_t it = { + .real_world = world, + .world = chain_it->world, + .terms = term, + .field_count = 1, + .chain_it = ECS_CONST_CAST(ecs_iter_t*, chain_it), + .next = ecs_term_next + }; + + flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); + + term_iter_init(world, term, &it.priv.iter.term, false); + + return it; +error: + return (ecs_iter_t){ 0 }; +} + +ecs_iter_t ecs_children( + const ecs_world_t *world, + ecs_entity_t parent) +{ + return ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(parent) }); +} + +bool ecs_children_next( + ecs_iter_t *it) +{ + return ecs_term_next(it); +} + +static +const ecs_table_record_t *flecs_term_iter_next_table( + ecs_term_iter_t *iter) +{ + ecs_id_record_t *idr = iter->cur; + if (!idr) { + return NULL; + } + + return flecs_table_cache_next(&iter->it, ecs_table_record_t); +} + +static +bool flecs_term_iter_find_superset( + ecs_world_t *world, + ecs_table_t *table, + ecs_term_t *term, + ecs_entity_t *source, + ecs_id_t *id, + int32_t *column) +{ + ecs_term_id_t *src = &term->src; + + /* Test if following the relationship finds the id */ + ecs_id_record_t *idr = term->idr; + if (world->flags & EcsWorldQuit) { + /* During world cleanup the keep_alive assert for id records is no + * longer enforced */ + idr = NULL; + } + int32_t index = flecs_search_relation_w_idr(world, table, 0, + term->id, src->trav, src->flags, source, id, 0, idr); + + if (index == -1) { + *source = 0; + return false; + } + + ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); + + *column = (index + 1) * -1; + + return true; +} + +static +bool flecs_term_iter_next( + ecs_world_t *world, + ecs_term_iter_t *iter, + bool match_prefab, + bool match_disabled) +{ + ecs_table_t *table = iter->table; + ecs_entity_t source = 0; + const ecs_table_record_t *tr; + ecs_term_t *term = &iter->term; + + do { + if (table) { + iter->cur_match ++; + if (iter->cur_match >= iter->match_count) { + table = NULL; + } else { + iter->last_column = ecs_search_offset( + world, table, iter->last_column + 1, term->id, 0); + iter->column = iter->last_column + 1; + if (iter->last_column >= 0) { + iter->id = table->type.array[iter->last_column]; + } + } + } + + if (!table) { + if (!(tr = flecs_term_iter_next_table(iter))) { + if (iter->cur != iter->set_index && iter->set_index != NULL) { + if (iter->observed_table_count != 0) { + iter->cur = iter->set_index; + if (iter->empty_tables) { + flecs_table_cache_all_iter( + &iter->set_index->cache, &iter->it); + } else { + flecs_table_cache_iter( + &iter->set_index->cache, &iter->it); + } + iter->index = 0; + tr = flecs_term_iter_next_table(iter); + } + } + + if (!tr) { + return false; + } + } + + table = tr->hdr.table; + if (table->_->traversable_count) { + iter->observed_table_count ++; + } + + if (!match_prefab && (table->flags & EcsTableIsPrefab)) { + continue; + } + + if (!match_disabled && (table->flags & EcsTableIsDisabled)) { + continue; + } + + iter->table = table; + iter->match_count = tr->count; + if (is_any_pair(term->id)) { + iter->match_count = 1; + } + + iter->cur_match = 0; + iter->last_column = tr->index; + iter->column = tr->index + 1; + iter->id = flecs_to_public_id(table->type.array[tr->index]); + } + + if (iter->cur == iter->set_index) { + if (iter->self_index) { + if (flecs_id_record_get_table(iter->self_index, table) != NULL) { + /* If the table has the id itself and this term matched Self + * we already matched it */ + continue; + } + } + + if (!flecs_term_iter_find_superset( + world, table, term, &source, &iter->id, &iter->column)) + { + continue; + } + + /* The tr->count field refers to the number of relationship instances, + * not to the number of matches. Superset terms can only yield a + * single match. */ + iter->match_count = 1; + } + + break; + } while (true); + + iter->subject = source; + + return true; +} + +static +bool flecs_term_iter_set_table( + ecs_world_t *world, + ecs_term_iter_t *iter, + ecs_table_t *table) +{ + const ecs_table_record_t *tr = NULL; + const ecs_id_record_t *idr = iter->self_index; + if (idr) { + tr = ecs_table_cache_get(&idr->cache, table); + if (tr) { + iter->match_count = tr->count; + iter->last_column = tr->index; + iter->column = tr->index + 1; + iter->id = flecs_to_public_id(table->type.array[tr->index]); + } + } + + if (!tr) { + idr = iter->set_index; + if (idr) { + tr = ecs_table_cache_get(&idr->cache, table); + if (!flecs_term_iter_find_superset(world, table, &iter->term, + &iter->subject, &iter->id, &iter->column)) + { + return false; + } + iter->match_count = 1; + } + } + + if (!tr) { + return false; + } + + /* Populate fields as usual */ + iter->table = table; + iter->cur_match = 0; + + return true; +} + +bool ecs_term_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); + + flecs_iter_validate(it); + + ecs_term_iter_t *iter = &it->priv.iter.term; + ecs_term_t *term = &iter->term; + ecs_world_t *world = it->real_world; + ecs_table_t *table; + + it->ids = &iter->id; + it->sources = &iter->subject; + it->columns = &iter->column; + it->terms = &iter->term; + it->sizes = &iter->size; + it->ptrs = &iter->ptr; + + ecs_iter_t *chain_it = it->chain_it; + if (chain_it) { + ecs_iter_next_action_t next = chain_it->next; + bool match; + + do { + if (!next(chain_it)) { + goto done; + } + + table = chain_it->table; + match = flecs_term_match_table(world, term, table, + it->ids, it->columns, it->sources, it->match_indices, true, + it->flags); + } while (!match); + goto yield; + + } else { + if (!flecs_term_iter_next(world, iter, + (term->flags & EcsTermMatchPrefab) != 0, + (term->flags & EcsTermMatchDisabled) != 0)) + { + goto done; + } + + table = iter->table; + + /* Source must either be 0 (EcsThis) or nonzero in case of substitution */ + ecs_assert(iter->subject || iter->cur != iter->set_index, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->table != NULL, ECS_INTERNAL_ERROR, NULL); + } + +yield: + flecs_iter_populate_data(world, it, table, 0, ecs_table_count(table), + it->ptrs); + ECS_BIT_SET(it->flags, EcsIterIsValid); + return true; +done: + ecs_iter_fini(it); +error: + return false; +} + +static +void flecs_init_filter_iter( + ecs_iter_t *it, + const ecs_filter_t *filter) +{ + ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); + it->priv.iter.filter.filter = filter; + it->field_count = filter->field_count; +} + +int32_t ecs_filter_pivot_term( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_term_t *terms = filter->terms; + int32_t i, term_count = filter->term_count; + int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_id_t id = term->id; + + if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { + continue; + } + + if (!ecs_term_match_this(term)) { + continue; + } + + ecs_id_record_t *idr = flecs_query_id_record_get(world, id); + if (!idr) { + /* If one of the terms does not match with any data, iterator + * should not return anything */ + return -2; /* -2 indicates filter doesn't match anything */ + } + + int32_t table_count = flecs_table_cache_count(&idr->cache); + if (min_count == -1 || table_count < min_count) { + min_count = table_count; + pivot_term = i; + if ((term->src.flags & EcsTraverseFlags) == EcsSelf) { + self_pivot_term = i; + } + } + } + + if (self_pivot_term != -1) { + pivot_term = self_pivot_term; + } + + return pivot_term; +error: + return -2; +} + +void flecs_filter_apply_iter_flags( + ecs_iter_t *it, + const ecs_filter_t *filter) +{ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, + ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced)); + ECS_BIT_COND(it->flags, EcsIterNoData, + ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); + ECS_BIT_COND(it->flags, EcsIterHasCondSet, + ECS_BIT_IS_SET(filter->flags, EcsFilterHasCondSet)); +} + +ecs_iter_t flecs_filter_iter_w_flags( + const ecs_world_t *stage, + const ecs_filter_t *filter, + ecs_flags32_t flags) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(filter->flags & (EcsFilterHasPred|EcsFilterHasScopes)), + ECS_UNSUPPORTED, NULL); + const ecs_world_t *world = ecs_get_world(stage); + + if (!(flags & EcsIterMatchVar)) { + flecs_process_pending_tables(world); + } + + ecs_iter_t it = { + .real_world = ECS_CONST_CAST(ecs_world_t*, world), + .world = ECS_CONST_CAST(ecs_world_t*, stage), + .query = filter, + .terms = filter ? filter->terms : NULL, + .next = ecs_filter_next, + .flags = flags, + .sizes = filter->sizes, + .system = filter->entity + }; + + ecs_filter_iter_t *iter = &it.priv.iter.filter; + iter->pivot_term = -1; + + flecs_init_filter_iter(&it, filter); + flecs_filter_apply_iter_flags(&it, filter); + + /* Find term that represents smallest superset */ + if (ECS_BIT_IS_SET(flags, EcsIterIgnoreThis)) { + term_iter_init_no_data(&iter->term_iter); + } else if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { + ecs_term_t *terms = filter->terms; + int32_t pivot_term = -1; + ecs_check(terms != NULL, ECS_INVALID_PARAMETER, NULL); + + pivot_term = ecs_filter_pivot_term(world, filter); + iter->kind = EcsIterEvalTables; + iter->pivot_term = pivot_term; + + if (pivot_term == -2) { + /* One or more terms have no matching results */ + term_iter_init_no_data(&iter->term_iter); + } else if (pivot_term == -1) { + /* No terms meet the criteria to be a pivot term, evaluate filter + * against all tables */ + term_iter_init_wildcard(world, &iter->term_iter, + ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); + } else { + ecs_assert(pivot_term >= 0, ECS_INTERNAL_ERROR, NULL); + term_iter_init(world, &terms[pivot_term], &iter->term_iter, + ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); + } + } else { + if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchAnything)) { + term_iter_init_no_data(&iter->term_iter); + } else { + iter->kind = EcsIterEvalNone; + } + } + + ECS_BIT_COND(it.flags, EcsIterNoData, + ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); + + if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { + /* Make space for one variable if the filter has terms for This var */ + it.variable_count = 1; + + /* Set variable name array */ + it.variable_names = ECS_CONST_CAST(char**, filter->variable_names); + } + + flecs_iter_init(stage, &it, flecs_iter_cache_all); + + return it; +error: + return (ecs_iter_t){ 0 }; +} + +ecs_iter_t ecs_filter_iter( + const ecs_world_t *stage, + const ecs_filter_t *filter) +{ + return flecs_filter_iter_w_flags(stage, filter, 0); +} + +ecs_iter_t ecs_filter_chain_iter( + const ecs_iter_t *chain_it, + const ecs_filter_t *filter) +{ + ecs_iter_t it = { + .query = filter, + .terms = filter->terms, + .field_count = filter->field_count, + .world = chain_it->world, + .real_world = chain_it->real_world, + .chain_it = ECS_CONST_CAST(ecs_iter_t*, chain_it), + .next = ecs_filter_next, + .sizes = filter->sizes, + .system = filter->entity + }; + + flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); + ecs_filter_iter_t *iter = &it.priv.iter.filter; + flecs_init_filter_iter(&it, filter); + + iter->kind = EcsIterEvalChain; + + return it; +} + +bool ecs_filter_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); +error: + return false; +} + +bool ecs_filter_next_instanced( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != it, ECS_INVALID_PARAMETER, NULL); + + ecs_filter_iter_t *iter = &it->priv.iter.filter; + const ecs_filter_t *filter = iter->filter; + ecs_world_t *world = it->real_world; + ecs_table_t *table = NULL; + bool match; + + flecs_iter_validate(it); + + ecs_iter_t *chain_it = it->chain_it; + ecs_iter_kind_t kind = iter->kind; + + if (chain_it) { + ecs_assert(kind == EcsIterEvalChain, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_next_action_t next = chain_it->next; + do { + if (!next(chain_it)) { + goto done; + } + + table = chain_it->table; + match = flecs_filter_match_table(world, filter, table, + it->ids, it->columns, it->sources, it->match_indices, NULL, + true, -1, it->flags); + } while (!match); + + goto yield; + } else if (kind == EcsIterEvalTables || kind == EcsIterEvalCondition) { + ecs_term_iter_t *term_iter = &iter->term_iter; + ecs_term_t *term = &term_iter->term; + int32_t pivot_term = iter->pivot_term; + bool first; + + /* Check if the This variable has been set on the iterator. If set, + * the filter should only be applied to the variable value */ + ecs_var_t *this_var = NULL; + ecs_table_t *this_table = NULL; + if (it->variable_count) { + if (ecs_iter_var_is_constrained(it, 0)) { + this_var = it->variables; + this_table = this_var->range.table; + + /* If variable is constrained, make sure it's a value that's + * pointing to a table, as a filter can't iterate single + * entities (yet) */ + ecs_assert(this_table != NULL, ECS_INVALID_OPERATION, NULL); + + /* Can't set variable for filter that does not iterate tables */ + ecs_assert(kind == EcsIterEvalTables, + ECS_INVALID_OPERATION, NULL); + } + } + + do { + /* If there are no matches left for the previous table, this is the + * first match of the next table. */ + first = iter->matches_left == 0; + + if (first) { + if (kind != EcsIterEvalCondition) { + /* Check if this variable was constrained */ + if (this_table != NULL) { + /* If this is the first match of a new result and the + * previous result was equal to the value of a + * constrained var, there's nothing left to iterate */ + if (it->table == this_table) { + goto done; + } + + /* If table doesn't match term iterator, it doesn't + * match filter. */ + if (!flecs_term_iter_set_table( + world, term_iter, this_table)) + { + goto done; + } + + it->offset = this_var->range.offset; + it->count = this_var->range.count; + + /* But if it does, forward it to filter matching */ + ecs_assert(term_iter->table == this_table, + ECS_INTERNAL_ERROR, NULL); + + /* If This variable is not constrained, iterate as usual */ + } else { + it->offset = 0; + it->count = 0; + + /* Find new match, starting with the leading term */ + if (!flecs_term_iter_next(world, term_iter, + ECS_BIT_IS_SET(filter->flags, + EcsFilterMatchPrefab), + ECS_BIT_IS_SET(filter->flags, + EcsFilterMatchDisabled))) + { + goto done; + } + } + + ecs_assert(term_iter->match_count != 0, + ECS_INTERNAL_ERROR, NULL); + + if (pivot_term == -1) { + /* Without a pivot term, we're iterating all tables with + * a wildcard, so the match count is meaningless. */ + term_iter->match_count = 1; + } else { + it->match_indices[pivot_term] = term_iter->match_count; + } + + iter->matches_left = term_iter->match_count; + + /* Filter iterator takes control over iterating all the + * permutations that match the wildcard. */ + term_iter->match_count = 1; + + table = term_iter->table; + + if (pivot_term != -1) { + int32_t index = term->field_index; + it->ids[index] = term_iter->id; + it->sources[index] = term_iter->subject; + it->columns[index] = term_iter->column; + } + } else { + /* Progress iterator to next match for table, if any */ + table = it->table; + if (term_iter->index == 0) { + iter->matches_left = 1; + term_iter->index = 1; /* prevents looping again */ + } else { + goto done; + } + } + + /* Match the remainder of the terms */ + match = flecs_filter_match_table(world, filter, table, + it->ids, it->columns, it->sources, + it->match_indices, &iter->matches_left, first, + pivot_term, it->flags); + if (!match) { + it->table = table; + iter->matches_left = 0; + continue; + } + + /* Table got matched, set This variable */ + if (table) { + ecs_assert(it->variable_count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); + it->variables[0].range.table = table; + } + + ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); + } + + /* If this is not the first result for the table, and the table + * is matched more than once, iterate remaining matches */ + if (!first && (iter->matches_left > 0)) { + table = it->table; + + /* Find first term that still has matches left */ + int32_t i, j, count = it->field_count; + for (i = count - 1; i >= 0; i --) { + int32_t mi = -- it->match_indices[i]; + if (mi) { + if (mi < 0) { + continue; + } + break; + } + } + + /* If matches_left > 0 we should've found at least one match */ + ecs_assert(i >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Progress first term to next match (must be at least one) */ + int32_t column = it->columns[i]; + if (column < 0) { + /* If this term was matched on a non-This entity, reconvert + * the column back to a positive value */ + column = -column; + } + + it->columns[i] = column + 1; + flecs_term_match_table(world, &filter->terms[i], table, + &it->ids[i], &it->columns[i], &it->sources[i], + &it->match_indices[i], false, it->flags); + + /* Reset remaining terms (if any) to first match */ + for (j = i + 1; j < count; j ++) { + flecs_term_match_table(world, &filter->terms[j], table, + &it->ids[j], &it->columns[j], &it->sources[j], + &it->match_indices[j], true, it->flags); + } + } + + match = iter->matches_left != 0; + iter->matches_left --; + + ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); + } while (!match); + + goto yield; + } + +done: +error: + ecs_iter_fini(it); + return false; + +yield: + if (!it->count && table) { + it->count = ecs_table_count(table); + } + flecs_iter_populate_data(world, it, table, it->offset, it->count, it->ptrs); + ECS_BIT_SET(it->flags, EcsIterIsValid); + return true; +} + +/** + * @file iter.c + * @brief Iterator API. + * + * The iterator API contains functions that apply to all iterators, such as + * resource management, or fetching resources for a matched table. The API also + * contains functions for generic iterators, which make it possible to iterate + * an iterator without needing to know what created the iterator. + */ + +#include + +/* Utility macros to enforce consistency when initializing iterator fields */ + +/* If term count is smaller than cache size, initialize with inline array, + * otherwise allocate. */ +#define INIT_CACHE(it, stack, fields, f, T, count)\ + if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ + it->f = flecs_stack_calloc_n(stack, T, count);\ + it->priv.cache.used |= flecs_iter_cache_##f;\ + } + +/* If array is allocated, free it when finalizing the iterator */ +#define FINI_CACHE(it, f, T, count)\ + if (it->priv.cache.used & flecs_iter_cache_##f) {\ + flecs_stack_free_n((void*)it->f, T, count);\ + } + +void* flecs_iter_calloc( + ecs_iter_t *it, + ecs_size_t size, + ecs_size_t align) +{ + ecs_world_t *world = it->world; + ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); + ecs_stack_t *stack = &stage->allocators.iter_stack; + return flecs_stack_calloc(stack, size, align); +} + +void flecs_iter_free( + void *ptr, + ecs_size_t size) +{ + flecs_stack_free(ptr, size); +} + +void flecs_iter_init( + const ecs_world_t *world, + ecs_iter_t *it, + ecs_flags8_t fields) +{ + ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INTERNAL_ERROR, NULL); + + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + ecs_stack_t *stack = &stage->allocators.iter_stack; + + it->priv.cache.used = 0; + it->priv.cache.allocated = 0; + it->priv.cache.stack_cursor = flecs_stack_get_cursor(stack); + it->priv.entity_iter = flecs_stack_calloc_t( + stack, ecs_entity_filter_iter_t); + + INIT_CACHE(it, stack, fields, ids, ecs_id_t, it->field_count); + INIT_CACHE(it, stack, fields, sources, ecs_entity_t, it->field_count); + INIT_CACHE(it, stack, fields, match_indices, int32_t, it->field_count); + INIT_CACHE(it, stack, fields, columns, int32_t, it->field_count); + INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); + INIT_CACHE(it, stack, fields, ptrs, void*, it->field_count); +} + +void flecs_iter_validate( + ecs_iter_t *it) +{ + ECS_BIT_SET(it->flags, EcsIterIsValid); + + /* Make sure multithreaded iterator isn't created for real world */ + ecs_world_t *world = it->real_world; + ecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldMultiThreaded) || it->world != it->real_world, + ECS_INVALID_PARAMETER, + "create iterator for stage when world is in multithreaded mode"); + (void)world; +error: + return; +} + +void ecs_iter_fini( + ecs_iter_t *it) +{ + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + + if (it->fini) { + it->fini(it); + } + + ecs_world_t *world = it->world; + if (!world) { + return; + } + + FINI_CACHE(it, ids, ecs_id_t, it->field_count); + FINI_CACHE(it, sources, ecs_entity_t, it->field_count); + FINI_CACHE(it, match_indices, int32_t, it->field_count); + FINI_CACHE(it, columns, int32_t, it->field_count); + FINI_CACHE(it, variables, ecs_var_t, it->variable_count); + FINI_CACHE(it, ptrs, void*, it->field_count); + flecs_stack_free_t(it->priv.entity_iter, ecs_entity_filter_iter_t); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_stack_restore_cursor(&stage->allocators.iter_stack, + it->priv.cache.stack_cursor); +} + +static +bool flecs_iter_populate_term_data( + ecs_world_t *world, + ecs_iter_t *it, + int32_t t, + int32_t column, + void **ptr_out) +{ + bool is_shared = false; + ecs_table_t *table; + void *data; + int32_t row, u_index; + + if (!column) { + /* Term has no data. This includes terms that have Not operators. */ + goto no_data; + } + + /* Filter terms may match with data but don't return it */ + if (it->terms[t].inout == EcsInOutNone) { + goto no_data; + } + + ecs_assert(it->sizes != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t size = it->sizes[t]; + if (!size) { + goto no_data; + } + + if (column < 0) { + table = it->table; + is_shared = true; + + /* Data is not from This */ + if (it->references && (!table || !(table->flags & EcsTableHasTarget))) { + /* The reference array is used only for components matched on a + * table (vs. individual entities). Remaining components should be + * assigned outside of this function */ + if (ecs_term_match_this(&it->terms[t])) { + + /* Iterator provides cached references for non-This terms */ + ecs_ref_t *ref = &it->references[-column - 1]; + if (ptr_out) { + if (ref->id) { + ptr_out[0] = (void*)ecs_ref_get_id(world, ref, ref->id); + } else { + ptr_out[0] = NULL; + } + } + + if (!ref->id) { + is_shared = false; + } + + return is_shared; + } + + return true; + } else { + ecs_entity_t subj = it->sources[t]; + ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); + + /* Don't use ecs_get_id directly. Instead, go directly to the + * storage so that we can get both the pointer and size */ + ecs_record_t *r = flecs_entities_get(world, subj); + ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); + + row = ECS_RECORD_TO_ROW(r->row); + table = r->table; + + ecs_id_t id = it->ids[t]; + ecs_table_record_t *tr; + + if (!(tr = flecs_table_record_get(world, table, id)) || (tr->column == -1)) { + u_index = flecs_table_column_to_union_index(table, -column - 1); + if (u_index != -1) { + goto has_union; + } + goto no_data; + } + + /* We now have row and column, so we can get the storage for the id + * which gives us the pointer and size */ + column = tr->column; + ecs_vec_t *s = &table->data.columns[column].data; + data = ecs_vec_first(s); + /* Fallthrough to has_data */ + } + } else { + /* Data is from This, use table from iterator */ + table = it->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + row = it->offset; + + int32_t storage_column = ecs_table_type_to_column_index( + table, column - 1); + if (storage_column == -1) { + u_index = flecs_table_column_to_union_index(table, column - 1); + if (u_index != -1) { + goto has_union; + } + goto no_data; + } + + if (!it->count) { + goto no_data; + } + + ecs_vec_t *s = &table->data.columns[storage_column].data; + data = ecs_vec_first(s); + + /* Fallthrough to has_data */ + } + +has_data: + if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); + return is_shared; + +has_union: { + /* Edge case: if column is a switch we should return the vector with case + * identifiers. Will be replaced in the future with pluggable storage */ + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &table->_->sw_columns[u_index]; + data = ecs_vec_first(flecs_switch_values(sw)); + goto has_data; + } + +no_data: + if (ptr_out) ptr_out[0] = NULL; + return false; +} + +void flecs_iter_populate_data( + ecs_world_t *world, + ecs_iter_t *it, + ecs_table_t *table, + int32_t offset, + int32_t count, + void **ptrs) +{ + ecs_table_t *prev_table = it->table; + if (prev_table) { + it->frame_offset += ecs_table_count(prev_table); + } + + it->table = table; + it->offset = offset; + it->count = count; + if (table) { + ecs_assert(count != 0 || !ecs_table_count(table) || (it->flags & EcsIterTableOnly), + ECS_INTERNAL_ERROR, NULL); + if (count) { + it->entities = ecs_vec_get_t( + &table->data.entities, ecs_entity_t, offset); + } else { + it->entities = NULL; + } + } + + int t, field_count = it->field_count; + if (ECS_BIT_IS_SET(it->flags, EcsIterNoData)) { + ECS_BIT_CLEAR(it->flags, EcsIterHasShared); + return; + } + + bool has_shared = false; + if (ptrs) { + for (t = 0; t < field_count; t ++) { + int32_t column = it->columns[t]; + has_shared |= flecs_iter_populate_term_data(world, it, t, column, + &ptrs[t]); + } + } + + ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); +} + +bool flecs_iter_next_row( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); + + bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + if (!is_instanced) { + int32_t instance_count = it->instance_count; + int32_t count = it->count; + int32_t offset = it->offset; + + if (instance_count > count && offset < (instance_count - 1)) { + ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); + int t, field_count = it->field_count; + + for (t = 0; t < field_count; t ++) { + ecs_entity_t src = it->sources[t]; + if (!src) { + void *ptr = it->ptrs[t]; + if (ptr) { + it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); + } + } + } + + if (it->entities) { + it->entities ++; + } + it->offset ++; + + return true; + } + } + + return false; +} + +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result) +{ + it->instance_count = it->count; + bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + bool has_shared = ECS_BIT_IS_SET(it->flags, EcsIterHasShared); + if (result && !is_instanced && it->count && has_shared) { + it->count = 1; + } + + return result; +} + +/* --- Public API --- */ + +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + + ecs_check(!size || ecs_field_size(it, index) == size || + (!ecs_field_size(it, index) && (!it->ptrs[index - 1])), + ECS_INVALID_PARAMETER, NULL); + (void)size; + + return it->ptrs[index - 1]; +error: + return NULL; +} + +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int32_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + ecs_term_t *term = &it->terms[index - 1]; + + if (term->inout == EcsIn) { + return true; + } else if (term->inout == EcsInOutDefault) { + if (!ecs_term_match_this(term)) { + return true; + } + + ecs_term_id_t *src = &term->src; + if (!(src->flags & EcsSelf)) { + return true; + } + } +error: + return false; +} + +bool ecs_field_is_writeonly( + const ecs_iter_t *it, + int32_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + ecs_term_t *term = &it->terms[index - 1]; + return term->inout == EcsOut; +error: + return false; +} + +bool ecs_field_is_set( + const ecs_iter_t *it, + int32_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + int32_t column = it->columns[index - 1]; + if (!column) { + return false; + } else if (column < 0) { + if (it->references) { + column = -column - 1; + ecs_ref_t *ref = &it->references[column]; + return ref->entity != 0; + } else { + return true; + } + } + return true; +error: + return false; +} + +bool ecs_field_is_self( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + return it->sources == NULL || it->sources[index - 1] == 0; +} + +ecs_id_t ecs_field_id( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + return it->ids[index - 1]; +} + +int32_t ecs_field_column_index( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + int32_t result = it->columns[index - 1]; + if (result <= 0) { + return -1; + } else { + return result - 1; + } +} + +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + if (it->sources) { + return it->sources[index - 1]; + } else { + return 0; + } +} + +size_t ecs_field_size( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + return (size_t)it->sizes[index - 1]; +} + +char* ecs_iter_str( + const ecs_iter_t *it) +{ + if (!(it->flags & EcsIterIsValid)) { + return NULL; + } + + ecs_world_t *world = it->world; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + int i; + + if (it->field_count) { + ecs_strbuf_list_push(&buf, "id: ", ","); + for (i = 0; i < it->field_count; i ++) { + ecs_id_t id = ecs_field_id(it, i + 1); + char *str = ecs_id_str(world, id); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); + + ecs_strbuf_list_push(&buf, "src: ", ","); + for (i = 0; i < it->field_count; i ++) { + ecs_entity_t subj = ecs_field_src(it, i + 1); + char *str = ecs_get_fullpath(world, subj); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); + + ecs_strbuf_list_push(&buf, "set: ", ","); + for (i = 0; i < it->field_count; i ++) { + if (ecs_field_is_set(it, i + 1)) { + ecs_strbuf_list_appendlit(&buf, "true"); + } else { + ecs_strbuf_list_appendlit(&buf, "false"); + } + } + ecs_strbuf_list_pop(&buf, "\n"); + } + + if (it->variable_count && it->variable_names) { + int32_t actual_count = 0; + for (i = 0; i < it->variable_count; i ++) { + const char *var_name = it->variable_names[i]; + if (!var_name || var_name[0] == '_' || !strcmp(var_name, "This")) { + /* Skip anonymous variables */ + continue; + } + + ecs_var_t var = it->variables[i]; + if (!var.entity) { + /* Skip table variables */ + continue; + } + + if (!actual_count) { + ecs_strbuf_list_push(&buf, "var: ", ","); + } + + char *str = ecs_get_fullpath(world, var.entity); + ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); + ecs_os_free(str); + + actual_count ++; + } + if (actual_count) { + ecs_strbuf_list_pop(&buf, "\n"); + } + } + + if (it->count) { + ecs_strbuf_appendlit(&buf, "this:\n"); + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + char *str = ecs_get_fullpath(world, e); + ecs_strbuf_appendlit(&buf, " - "); + ecs_strbuf_appendstr(&buf, str); + ecs_strbuf_appendch(&buf, '\n'); + ecs_os_free(str); + } + } + + return ecs_strbuf_get(&buf); +} + +void ecs_iter_poly( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter_out, + ecs_term_t *filter) +{ + ecs_iterable_t *iterable = ecs_get_iterable(poly); + iterable->init(world, poly, iter_out, filter); +} + +bool ecs_iter_next( + ecs_iter_t *iter) +{ + ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); + return iter->next(iter); +error: + return false; +} + +int32_t ecs_iter_count( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->flags, EcsIterNoData); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + int32_t count = 0; + while (ecs_iter_next(it)) { + count += it->count; + } + return count; +error: + return 0; +} + +ecs_entity_t ecs_iter_first( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->flags, EcsIterNoData); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + ecs_entity_t result = 0; + if (ecs_iter_next(it)) { + result = it->entities[0]; + ecs_iter_fini(it); + } + + return result; +error: + return 0; +} + +bool ecs_iter_is_true( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->flags, EcsIterNoData); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + bool result = ecs_iter_next(it); + if (result) { + ecs_iter_fini(it); + } + return result; +error: + return false; +} + +ecs_entity_t ecs_iter_get_var( + ecs_iter_t *it, + int32_t var_id) +{ + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_var_t *var = &it->variables[var_id]; + ecs_entity_t e = var->entity; + if (!e) { + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; + } + if (table) { + if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { + ecs_assert(ecs_table_count(table) > var->range.offset, + ECS_INTERNAL_ERROR, NULL); + e = ecs_vec_get_t(&table->data.entities, ecs_entity_t, + var->range.offset)[0]; + } + } + } else { + ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); + } + + return e; +error: + return 0; +} + +ecs_table_t* ecs_iter_get_var_as_table( + ecs_iter_t *it, + int32_t var_id) +{ + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_var_t *var = &it->variables[var_id]; + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; + } + + if (!table) { + /* If table is not set, try to get table from entity */ + ecs_entity_t e = var->entity; + if (e) { + ecs_record_t *r = flecs_entities_get(it->real_world, e); + if (r) { + table = r->table; + if (ecs_table_count(table) != 1) { + /* If table contains more than the entity, make sure not to + * return a partial table. */ + return NULL; + } + } + } + } + + if (table) { + if (var->range.offset) { + /* Don't return whole table if only partial table is matched */ + return NULL; + } + + if (!var->range.count || ecs_table_count(table) == var->range.count) { + /* Return table if count matches */ + return table; + } + } + +error: + return NULL; +} + +ecs_table_range_t ecs_iter_get_var_as_range( + ecs_iter_t *it, + int32_t var_id) +{ + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_range_t result = { 0 }; + + ecs_var_t *var = &it->variables[var_id]; + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; + } + + if (!table) { + ecs_entity_t e = var->entity; + if (e) { + ecs_record_t *r = flecs_entities_get(it->real_world, e); + if (r) { + result.table = r->table; + result.offset = ECS_RECORD_TO_ROW(r->row); + result.count = 1; + } + } + } else { + result.table = table; + result.offset = var->range.offset; + result.count = var->range.count; + if (!result.count) { + result.count = ecs_table_count(table); + } + } + + return result; +error: + return (ecs_table_range_t){0}; +} + +void ecs_iter_set_var( + ecs_iter_t *it, + int32_t var_id, + ecs_entity_t entity) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < FLECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + /* Can't set variable while iterating */ + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); + ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_var_t *var = &it->variables[var_id]; + var->entity = entity; + + ecs_record_t *r = flecs_entities_get(it->real_world, entity); + if (r) { + var->range.table = r->table; + var->range.offset = ECS_RECORD_TO_ROW(r->row); + var->range.count = 1; + } else { + var->range.table = NULL; + var->range.offset = 0; + var->range.count = 0; + } + + it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); + + if (it->set_var) { + it->set_var(it); + } + +error: + return; +} + +void ecs_iter_set_var_as_table( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_t *table) +{ + ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; + ecs_iter_set_var_as_range(it, var_id, &range); +} + +void ecs_iter_set_var_as_range( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_range_t *range) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < FLECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!range->offset || range->offset < ecs_table_count(range->table), + ECS_INVALID_PARAMETER, NULL); + ecs_check((range->offset + range->count) <= ecs_table_count(range->table), + ECS_INVALID_PARAMETER, NULL); + + /* Can't set variable while iterating */ + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, NULL); + + ecs_var_t *var = &it->variables[var_id]; + var->range = *range; + + if (range->count == 1) { + ecs_table_t *table = range->table; + var->entity = ecs_vec_get_t( + &table->data.entities, ecs_entity_t, range->offset)[0]; + } else { + var->entity = 0; + } + + it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); + +error: + return; +} + +bool ecs_iter_var_is_constrained( + ecs_iter_t *it, + int32_t var_id) +{ + return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; +} + +static +void ecs_chained_iter_fini( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_fini(it->chain_it); + + it->chain_it = NULL; +} + +ecs_iter_t ecs_page_iter( + const ecs_iter_t *it, + int32_t offset, + int32_t limit) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_t result = *it; + result.priv.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ + + result.priv.iter.page = (ecs_page_iter_t){ + .offset = offset, + .limit = limit, + .remaining = limit + }; + result.next = ecs_page_next; + result.fini = ecs_chained_iter_fini; + result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); + + return result; +error: + return (ecs_iter_t){ 0 }; +} + +static +void flecs_offset_iter( + ecs_iter_t *it, + int32_t offset) +{ + it->entities = &it->entities[offset]; + + int32_t t, field_count = it->field_count; + void **it_ptrs = it->ptrs; + if (it_ptrs) { + for (t = 0; t < field_count; t ++) { + void *ptrs = it_ptrs[t]; + if (!ptrs) { + continue; + } + + if (it->sources[t]) { + continue; + } + + it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); + } + } +} + +static +bool ecs_page_next_instanced( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_t *chain_it = it->chain_it; + bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + + do { + if (!ecs_iter_next(chain_it)) { + goto depleted; + } + + ecs_page_iter_t *iter = &it->priv.iter.page; + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); + + /* Keep instancing setting from original iterator */ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + + if (!chain_it->table) { + goto yield; /* Task query */ + } + + int32_t offset = iter->offset; + int32_t limit = iter->limit; + if (!(offset || limit)) { + if (it->count) { + goto yield; + } else { + goto depleted; + } + } + + int32_t count = it->count; + int32_t remaining = iter->remaining; + + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + iter->offset -= count; + it->count = 0; + continue; + } else { + it->offset += offset; + count = it->count -= offset; + iter->offset = 0; + flecs_offset_iter(it, offset); + } + } + + if (remaining) { + if (remaining > count) { + iter->remaining -= count; + } else { + it->count = remaining; + iter->remaining = 0; + } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + goto done; + } + } while (it->count == 0); + +yield: + if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { + it->offset = 0; + } + + return true; +done: + /* Cleanup iterator resources if it wasn't yet depleted */ + ecs_iter_fini(chain_it); +depleted: +error: + return false; +} + +bool ecs_page_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); +error: + return false; +} + +ecs_iter_t ecs_worker_iter( + const ecs_iter_t *it, + int32_t index, + int32_t count) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_t result = *it; + result.priv.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ + + result.priv.iter.worker = (ecs_worker_iter_t){ + .index = index, + .count = count + }; + result.next = ecs_worker_next; + result.fini = ecs_chained_iter_fini; + result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); + + return result; +error: + return (ecs_iter_t){ 0 }; +} + +static +bool ecs_worker_next_instanced( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); + + bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + + ecs_iter_t *chain_it = it->chain_it; + ecs_worker_iter_t *iter = &it->priv.iter.worker; + int32_t res_count = iter->count, res_index = iter->index; + int32_t per_worker, instances_per_worker, first; + + do { + if (!ecs_iter_next(chain_it)) { + return false; + } + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); + + /* Keep instancing setting from original iterator */ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + + int32_t count = it->count; + int32_t instance_count = it->instance_count; + per_worker = count / res_count; + instances_per_worker = instance_count / res_count; + first = per_worker * res_index; + count -= per_worker * res_count; + + if (count) { + if (res_index < count) { + per_worker ++; + first += res_index; + } else { + first += count; + } + } + + if (!per_worker && it->table == NULL) { + if (res_index == 0) { + return true; + } else { + // chained iterator was not yet cleaned up + // since it returned true from ecs_iter_next, so clean it up here. + ecs_iter_fini(chain_it); + return false; + } + } + } while (!per_worker); + + it->instance_count = instances_per_worker; + it->frame_offset += first; + + flecs_offset_iter(it, it->offset + first); + it->count = per_worker; + + if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { + it->offset += first; + } else { + it->offset = 0; + } + + return true; +error: + return false; +} + +bool ecs_worker_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); +error: + return false; +} + +/** + * @file misc.c + * @brief Miscellaneous functions. + */ + +#include +#include + +#ifndef FLECS_NDEBUG +static int64_t flecs_s_min[] = { + [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; +static int64_t flecs_s_max[] = { + [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; +static uint64_t flecs_u_max[] = { + [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; + +uint64_t flecs_ito_( + size_t size, + bool is_signed, + bool lt_zero, + uint64_t u, + const char *err) +{ + union { + uint64_t u; + int64_t s; + } v; + + v.u = u; + + if (is_signed) { + ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err); + ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err); + } else { + ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); + ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); + } + + return u; +} +#endif + +int32_t flecs_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; +} + +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); +} + +uint64_t flecs_string_hash( + const void *ptr) +{ + const ecs_hashed_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; +} + +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, 0, 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; +} + +char* ecs_asprintf( + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + char *result = ecs_vasprintf(fmt, args); + va_end(args); + return result; +} + +char* flecs_to_snake_case(const char *str) { + int32_t upper_count = 0, len = 1; + const char *ptr = str; + char ch, *out, *out_ptr; + + for (ptr = &str[1]; (ch = *ptr); ptr ++) { + if (isupper(ch)) { + upper_count ++; + } + len ++; + } + + out = out_ptr = ecs_os_malloc_n(char, len + upper_count + 1); + for (ptr = str; (ch = *ptr); ptr ++) { + if (isupper(ch)) { + if ((ptr != str) && (out_ptr[-1] != '_')) { + out_ptr[0] = '_'; + out_ptr ++; + } + out_ptr[0] = (char)tolower(ch); + out_ptr ++; + } else { + out_ptr[0] = ch; + out_ptr ++; + } + } + + out_ptr[0] = '\0'; + + return out; +} + +/** + * @file observable.c + * @brief Observable implementation. + * + * The observable implementation contains functions that find the set of + * observers to invoke for an event. The code also contains the implementation + * of a reachable id cache, which is used to speedup event propagation when + * relationships are added/removed to/from entities. + */ + + +void flecs_observable_init( + ecs_observable_t *observable) +{ + flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t); + observable->on_add.event = EcsOnAdd; + observable->on_remove.event = EcsOnRemove; + observable->on_set.event = EcsOnSet; + observable->un_set.event = EcsUnSet; +} + +void flecs_observable_fini( + ecs_observable_t *observable) +{ + ecs_assert(!ecs_map_is_init(&observable->on_add.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_init(&observable->on_remove.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_init(&observable->on_set.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_init(&observable->un_set.event_ids), + ECS_INTERNAL_ERROR, NULL); + + ecs_sparse_t *events = &observable->events; + int32_t i, count = flecs_sparse_count(events); + for (i = 0; i < count; i ++) { + ecs_event_record_t *er = + flecs_sparse_get_dense_t(events, ecs_event_record_t, i); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + (void)er; + + /* All triggers should've unregistered by now */ + ecs_assert(!ecs_map_is_init(&er->event_ids), + ECS_INTERNAL_ERROR, NULL); + } + + flecs_sparse_fini(&observable->events); +} + +ecs_event_record_t* flecs_event_record_get( + const ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Builtin events*/ + if (event == EcsOnAdd) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add); + else if (event == EcsOnRemove) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove); + else if (event == EcsOnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set); + else if (event == EcsUnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->un_set); + else if (event == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard); + + /* User events */ + return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); +} + +ecs_event_record_t* flecs_event_record_ensure( + ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_event_record_t *er = flecs_event_record_get(o, event); + if (er) { + return er; + } + er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event); + er->event = event; + return er; +} + +static +const ecs_event_record_t* flecs_event_record_get_if( + const ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_event_record_t *er = flecs_event_record_get(o, event); + if (er) { + if (ecs_map_is_init(&er->event_ids)) { + return er; + } + if (er->any) { + return er; + } + if (er->wildcard) { + return er; + } + if (er->wildcard_pair) { + return er; + } + } + + return NULL; +} + +ecs_event_id_record_t* flecs_event_id_record_get( + const ecs_event_record_t *er, + ecs_id_t id) +{ + if (!er) { + return NULL; + } + + if (id == EcsAny) return er->any; + else if (id == EcsWildcard) return er->wildcard; + else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; + else { + if (ecs_map_is_init(&er->event_ids)) { + return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id); + } + return NULL; + } +} + +static +ecs_event_id_record_t* flecs_event_id_record_get_if( + const ecs_event_record_t *er, + ecs_id_t id) +{ + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (!ider) { + return NULL; + } + + if (ider->observer_count) { + return ider; + } + + return NULL; +} + +ecs_event_id_record_t* flecs_event_id_record_ensure( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_id_t id) +{ + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (ider) { + return ider; + } + + ider = ecs_os_calloc_t(ecs_event_id_record_t); + + if (id == EcsAny) { + return er->any = ider; + } else if (id == EcsWildcard) { + return er->wildcard = ider; + } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + return er->wildcard_pair = ider; + } + + ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); + ecs_map_insert_ptr(&er->event_ids, id, ider); + return ider; +} + +void flecs_event_id_record_remove( + ecs_event_record_t *er, + ecs_id_t id) +{ + if (id == EcsAny) { + er->any = NULL; + } else if (id == EcsWildcard) { + er->wildcard = NULL; + } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + er->wildcard_pair = NULL; + } else { + ecs_map_remove(&er->event_ids, id); + if (!ecs_map_count(&er->event_ids)) { + ecs_map_fini(&er->event_ids); + } + } +} + +static +int32_t flecs_event_observers_get( + const ecs_event_record_t *er, + ecs_id_t id, + ecs_event_id_record_t **iders) +{ + if (!er) { + return 0; + } + + /* Populate array with observer sets matching the id */ + int32_t count = 0; + + if (id != EcsAny) { + iders[0] = flecs_event_id_record_get_if(er, EcsAny); + count += iders[count] != 0; + } + + iders[count] = flecs_event_id_record_get_if(er, id); + count += iders[count] != 0; + + if (id != EcsAny) { + if (ECS_IS_PAIR(id)) { + ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); + ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); + ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); + iders[count] = flecs_event_id_record_get_if(er, id_fwc); + count += iders[count] != 0; + iders[count] = flecs_event_id_record_get_if(er, id_swc); + count += iders[count] != 0; + iders[count] = flecs_event_id_record_get_if(er, id_pwc); + count += iders[count] != 0; + } else { + iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); + count += iders[count] != 0; + } + } + + return count; +} + +bool flecs_observers_exist( + ecs_observable_t *observable, + ecs_id_t id, + ecs_entity_t event) +{ + const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + if (!er) { + return false; + } + + return flecs_event_id_record_get_if(er, id) != NULL; +} + +static +void flecs_emit_propagate( + ecs_world_t *world, + ecs_iter_t *it, + ecs_id_record_t *idr, + ecs_id_record_t *tgt_idr, + ecs_entity_t trav, + ecs_event_id_record_t **iders, + int32_t ider_count); + +static +void flecs_emit_propagate_id( + ecs_world_t *world, + ecs_iter_t *it, + ecs_id_record_t *idr, + ecs_id_record_t *cur, + ecs_entity_t trav, + ecs_event_id_record_t **iders, + int32_t ider_count) +{ + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + return; + } + + const ecs_table_record_t *tr; + int32_t event_cur = it->event_cur; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!ecs_table_count(table)) { + continue; + } + + bool owned = flecs_id_record_get_table(idr, table) != NULL; + + int32_t e, entity_count = ecs_table_count(table); + it->table = table; + it->other_table = NULL; + it->offset = 0; + it->count = entity_count; + if (entity_count) { + it->entities = ecs_vec_first(&table->data.entities); + } + + /* Treat as new event as this could invoke observers again for + * different tables. */ + it->event_cur = ++ world->event_id; + + int32_t ider_i; + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + if (!owned) { + /* Owned takes precedence */ + flecs_observers_invoke(world, &ider->self_up, it, table, trav); + } + } + + if (!table->_->traversable_count) { + continue; + } + + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + for (e = 0; e < entity_count; e ++) { + ecs_record_t *r = flecs_entities_get(world, entities[e]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *idr_t = r->idr; + if (idr_t) { + /* Only notify for entities that are used in pairs with + * traversable relationships */ + flecs_emit_propagate(world, it, idr, idr_t, trav, + iders, ider_count); + } + } + } + + it->event_cur = event_cur; +} + +static +void flecs_emit_propagate( + ecs_world_t *world, + ecs_iter_t *it, + ecs_id_record_t *idr, + ecs_id_record_t *tgt_idr, + ecs_entity_t propagate_trav, + ecs_event_id_record_t **iders, + int32_t ider_count) +{ + ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("propagate events/invalidate cache for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + + /* Propagate to records of traversable relationships */ + ecs_id_record_t *cur = tgt_idr; + while ((cur = cur->trav.next)) { + cur->reachable.generation ++; /* Invalidate cache */ + + /* Get traversed relationship */ + ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); + if (propagate_trav && propagate_trav != trav) { + continue; + } + + flecs_emit_propagate_id( + world, it, idr, cur, trav, iders, ider_count); + } + + ecs_log_pop_3(); +} + +static +void flecs_emit_propagate_invalidate_tables( + ecs_world_t *world, + ecs_id_record_t *tgt_idr) +{ + ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("invalidate reachable cache for %s", idstr); + ecs_os_free(idstr); + } + + /* Invalidate records of traversable relationships */ + ecs_id_record_t *cur = tgt_idr; + while ((cur = cur->trav.next)) { + ecs_reachable_cache_t *rc = &cur->reachable; + if (rc->current != rc->generation) { + /* Subtree is already marked invalid */ + continue; + } + + rc->generation ++; + + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + continue; + } + + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } + + int32_t e, entity_count = ecs_table_count(table); + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + + for (e = 0; e < entity_count; e ++) { + ecs_record_t *r = flecs_entities_get(world, entities[e]); + ecs_id_record_t *idr_t = r->idr; + if (idr_t) { + /* Only notify for entities that are used in pairs with + * traversable relationships */ + flecs_emit_propagate_invalidate_tables(world, idr_t); + } + } + } + } +} + +void flecs_emit_propagate_invalidate( + ecs_world_t *world, + ecs_table_t *table, + int32_t offset, + int32_t count) +{ + ecs_entity_t *entities = ecs_vec_get_t(&table->data.entities, + ecs_entity_t, offset); + int32_t i; + for (i = 0; i < count; i ++) { + ecs_record_t *record = flecs_entities_get(world, entities[i]); + if (!record) { + /* If the event is emitted after a bulk operation, it's possible + * that it hasn't been populated with entities yet. */ + continue; + } + + ecs_id_record_t *idr_t = record->idr; + if (idr_t) { + /* Event is used as target in traversable relationship, propagate */ + flecs_emit_propagate_invalidate_tables(world, idr_t); + } + } +} + +static +void flecs_override_copy( + ecs_world_t *world, + ecs_table_t *table, + const ecs_type_info_t *ti, + void *dst, + const void *src, + int32_t offset, + int32_t count) +{ + void *ptr = dst; + ecs_copy_t copy = ti->hooks.copy; + ecs_size_t size = ti->size; + int32_t i; + if (copy) { + for (i = 0; i < count; i ++) { + copy(ptr, src, count, ti); + ptr = ECS_OFFSET(ptr, size); + } + } else { + for (i = 0; i < count; i ++) { + ecs_os_memcpy(ptr, src, size); + ptr = ECS_OFFSET(ptr, size); + } + } + + ecs_iter_action_t on_set = ti->hooks.on_set; + if (on_set) { + ecs_entity_t *entities = ecs_vec_get_t( + &table->data.entities, ecs_entity_t, offset); + flecs_invoke_hook(world, table, count, offset, entities, + dst, ti->component, ti, EcsOnSet, on_set); + } +} + +static +void* flecs_override( + ecs_iter_t *it, + const ecs_type_t *emit_ids, + ecs_id_t id, + ecs_table_t *table, + ecs_id_record_t *idr) +{ + if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { + return NULL; + } + + int32_t i = 0, count = emit_ids->count; + ecs_id_t *ids = emit_ids->array; + for (i = 0; i < count; i ++) { + if (ids[i] == id) { + /* If an id was both inherited and overridden in the same event + * (like what happens during an auto override), we need to copy the + * value of the inherited component to the new component. + * Also flag to the callee that this component was overridden, so + * that an OnSet event can be emmitted for it. + * Note that this is different from a component that was overridden + * after it was inherited, as this does not change the actual value + * of the component for the entity (it is copied from the existing + * overridden component), and does not require an OnSet event. */ + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + continue; + } + + int32_t index = tr->column; + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + + ecs_column_t *column = &table->data.columns[index]; + ecs_size_t size = column->ti->size; + return ecs_vec_get(&column->data, size, it->offset); + } + } + + return NULL; +} + +static +void flecs_emit_forward_up( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids); + +static +void flecs_emit_forward_id( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + int32_t column, + int32_t offset, + ecs_entity_t trav) +{ + ecs_id_t id = idr->id; + ecs_entity_t event = er ? er->event : 0; + bool inherit = trav == EcsIsA; + bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); + ecs_event_id_record_t *iders[5]; + ecs_event_id_record_t *iders_onset[5]; + + /* Skip id if there are no observers for it */ + int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); + int32_t ider_onset_i, ider_onset_count = 0; + if (er_onset) { + ider_onset_count = flecs_event_observers_get( + er_onset, id, iders_onset); + } + + if (!may_override && (!ider_count && !ider_onset_count)) { + return; + } + + it->ids[0] = id; + it->sources[0] = tgt; + it->event_id = id; + it->ptrs[0] = NULL; + it->sizes[0] = 0; + + int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); + if (storage_i != -1) { + ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_column_t *c = &tgt_table->data.columns[storage_i]; + it->ptrs[0] = ecs_vec_get(&c->data, c->ti->size, offset); + it->sizes[0] = c->ti->size; + } + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + bool owned = tr != NULL; + + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke(world, &ider->self_up, it, table, trav); + } + } + + /* Emit OnSet events for newly inherited components */ + if (storage_i != -1) { + bool override = false; + + /* If component was added together with IsA relationship, still emit + * OnSet event, as it's a new value for the entity. */ + void *base_ptr = it->ptrs[0]; + void *ptr = flecs_override(it, emit_ids, id, table, idr); + if (ptr) { + override = true; + it->ptrs[0] = ptr; + } + + if (ider_onset_count) { + it->event = er_onset->event; + + for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) { + ecs_event_id_record_t *ider = iders_onset[ider_onset_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke( + world, &ider->self_up, it, table, trav); + } else if (override) { + ecs_entity_t src = it->sources[0]; + it->sources[0] = 0; + flecs_observers_invoke(world, &ider->self, it, table, 0); + flecs_observers_invoke(world, &ider->self_up, it, table, 0); + it->sources[0] = src; + } + } + + it->event = event; + it->ptrs[0] = base_ptr; + } + } +} + +static +void flecs_emit_forward_and_cache_id( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_entity_t tgt, + ecs_record_t *tgt_record, + ecs_table_t *tgt_table, + const ecs_table_record_t *tgt_tr, + int32_t column, + int32_t offset, + ecs_vec_t *reachable_ids, + ecs_entity_t trav) +{ + /* Cache forwarded id for (rel, tgt) pair */ + ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator, + reachable_ids, ecs_reachable_elem_t); + elem->tr = tgt_tr; + elem->record = tgt_record; + elem->src = tgt; + elem->id = idr->id; +#ifndef NDEBUG + elem->table = tgt_table; +#endif + ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); + + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, + tgt, tgt_table, column, offset, trav); +} + +static +int32_t flecs_emit_stack_at( + ecs_vec_t *stack, + ecs_id_record_t *idr) +{ + int32_t sp = 0, stack_count = ecs_vec_count(stack); + ecs_table_t **stack_elems = ecs_vec_first(stack); + + for (sp = 0; sp < stack_count; sp ++) { + ecs_table_t *elem = stack_elems[sp]; + if (flecs_id_record_get_table(idr, elem)) { + break; + } + } + + return sp; +} + +static +bool flecs_emit_stack_has( + ecs_vec_t *stack, + ecs_id_record_t *idr) +{ + return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); +} + +static +void flecs_emit_forward_cached_ids( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_reachable_cache_t *rc, + ecs_vec_t *reachable_ids, + ecs_vec_t *stack, + ecs_entity_t trav) +{ + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t i, count = ecs_vec_count(&rc->ids); + for (i = 0; i < count; i ++) { + ecs_reachable_elem_t *rc_elem = &elems[i]; + const ecs_table_record_t *rc_tr = rc_elem->tr; + ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache; + ecs_record_t *rc_record = rc_elem->record; + + ecs_assert(rc_idr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_entities_get(world, rc_elem->src) == + rc_record, ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(rc_record->table == rc_elem->table, + ECS_INTERNAL_ERROR, NULL); + + if (flecs_emit_stack_has(stack, rc_idr)) { + continue; + } + + int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row); + flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, + it, table, rc_idr, rc_elem->src, + rc_record, rc_record->table, rc_tr, rc_tr->index, + rc_offset, reachable_ids, trav); + } +} + +static +void flecs_emit_dump_cache( + ecs_world_t *world, + const ecs_vec_t *vec) +{ + ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t); + for (int i = 0; i < ecs_vec_count(vec); i ++) { + ecs_reachable_elem_t *elem = &elems[i]; + char *idstr = ecs_id_str(world, elem->id); + char *estr = ecs_id_str(world, elem->src); + ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", + idstr, (uint32_t)elem->id, + estr, (uint32_t)elem->src, + elem->table); + ecs_os_free(idstr); + ecs_os_free(estr); + } + if (!ecs_vec_count(vec)) { + ecs_dbg_3("- no entries"); + } +} + +static +void flecs_emit_forward_table_up( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + ecs_record_t *tgt_record, + ecs_id_record_t *tgt_idr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids) +{ + ecs_allocator_t *a = &world->allocator; + int32_t i, id_count = tgt_table->type.count; + ecs_id_t *ids = tgt_table->type.array; + int32_t offset = ECS_RECORD_TO_ROW(tgt_record->row); + int32_t rc_child_offset = ecs_vec_count(reachable_ids); + int32_t stack_count = ecs_vec_count(stack); + + /* If tgt_idr is out of sync but is not the current id record being updated, + * keep track so that we can update two records for the cost of one. */ + ecs_reachable_cache_t *rc = &tgt_idr->reachable; + bool parent_revalidate = (reachable_ids != &rc->ids) && + (rc->current != rc->generation); + if (parent_revalidate) { + ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); + } + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("forward events from %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + + /* Function may have to copy values from overridden components if an IsA + * relationship was added together with other components. */ + ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id); + bool inherit = trav == EcsIsA; + + for (i = 0; i < id_count; i ++) { + ecs_id_t id = ids[i]; + ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache; + if (inherit && (idr->flags & EcsIdDontInherit)) { + continue; + } + + /* Id has the same relationship, traverse to find ids for forwarding */ + if (ECS_PAIR_FIRST(id) == trav) { + ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, + ecs_table_t*); + t[0] = tgt_table; + + ecs_reachable_cache_t *idr_rc = &idr->reachable; + if (idr_rc->current == idr_rc->generation) { + /* Cache hit, use cached ids to prevent traversing the same + * hierarchy multiple times. This especially speeds up code + * where (deep) hierarchies are created. */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, id); + ecs_dbg_3("forward cached for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, + table, idr_rc, reachable_ids, stack, trav); + ecs_log_pop_3(); + } else { + /* Cache is dirty, traverse upwards */ + do { + flecs_emit_forward_up(world, er, er_onset, emit_ids, it, + table, idr, stack, reachable_ids); + if (++i >= id_count) { + break; + } + + id = ids[i]; + if (ECS_PAIR_FIRST(id) != trav) { + break; + } + } while (true); + } + + ecs_vec_remove_last(stack); + continue; + } + + int32_t stack_at = flecs_emit_stack_at(stack, idr); + if (parent_revalidate && (stack_at == (stack_count - 1))) { + /* If parent id record needs to be revalidated, add id */ + ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, + ecs_reachable_elem_t); + elem->tr = tgt_tr; + elem->record = tgt_record; + elem->src = tgt; + elem->id = idr->id; +#ifndef NDEBUG + elem->table = tgt_table; +#endif + } + + /* Skip id if it's masked by a lower table in the tree */ + if (stack_at != stack_count) { + continue; + } + + flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, + table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, + offset, reachable_ids, trav); + } + + if (parent_revalidate) { + /* If this is not the current cache being updated, but it's marked + * as out of date, use intermediate results to populate cache. */ + int32_t rc_parent_offset = ecs_vec_count(&rc->ids); + + /* Only add ids that were added for this table */ + int32_t count = ecs_vec_count(reachable_ids); + count -= rc_child_offset; + + /* Append ids to any ids that already were added /*/ + if (count) { + ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); + ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, + ecs_reachable_elem_t, rc_parent_offset); + ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, + ecs_reachable_elem_t, rc_child_offset); + ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); + } + + rc->current = rc->generation; + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("cache revalidated for %s:", idstr); + ecs_os_free(idstr); + flecs_emit_dump_cache(world, &rc->ids); + } + } + + ecs_log_pop_3(); +} + +static +void flecs_emit_forward_up( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids) +{ + ecs_id_t id = idr->id; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + tgt = flecs_entities_get_generation(world, tgt); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *tgt_record = flecs_entities_try(world, tgt); + ecs_table_t *tgt_table; + if (!tgt_record || !(tgt_table = tgt_record->table)) { + return; + } + + flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, + tgt, tgt_table, tgt_record, idr, stack, reachable_ids); +} + +static +void flecs_emit_forward( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr) +{ + ecs_reachable_cache_t *rc = &idr->reachable; + + if (rc->current != rc->generation) { + /* Cache miss, iterate the tree to find ids to forward */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, idr->id); + ecs_dbg_3("reachable cache miss for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + + ecs_vec_t stack; + ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); + ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); + flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, + idr, &stack, &rc->ids); + it->sources[0] = 0; + ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); + + if (it->event == EcsOnAdd || it->event == EcsOnRemove) { + /* Only OnAdd/OnRemove events can validate top-level cache, which + * is for the id for which the event is emitted. + * The reason for this is that we don't want to validate the cache + * while the administration for the mutated entity isn't up to + * date yet. */ + rc->current = rc->generation; + } + + if (ecs_should_log_3()) { + ecs_dbg_3("cache after rebuild:"); + flecs_emit_dump_cache(world, &rc->ids); + } + + ecs_log_pop_3(); + } else { + /* Cache hit, use cached values instead of walking the tree */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, idr->id); + ecs_dbg_3("reachable cache hit for %s", idstr); + ecs_os_free(idstr); + flecs_emit_dump_cache(world, &rc->ids); + } + + ecs_entity_t trav = ECS_PAIR_FIRST(idr->id); + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t i, count = ecs_vec_count(&rc->ids); + for (i = 0; i < count; i ++) { + ecs_reachable_elem_t *elem = &elems[i]; + const ecs_table_record_t *tr = elem->tr; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_record_t *r = elem->record; + + ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_entities_get(world, elem->src) == r, + ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); + + int32_t offset = ECS_RECORD_TO_ROW(r->row); + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, + rc_idr, elem->src, r->table, tr->index, offset, trav); + } + } +} + +/* The emit function is responsible for finding and invoking the observers + * matching the emitted event. The function is also capable of forwarding events + * for newly reachable ids (after adding a relationship) and propagating events + * downwards. Both capabilities are not just useful in application logic, but + * are also an important building block for keeping query caches in sync. */ +void flecs_emit( + ecs_world_t *world, + ecs_world_t *stage, + ecs_event_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_time_t t = {0}; + bool measure_time = world->flags & EcsWorldMeasureSystemTime; + if (measure_time) { + ecs_time_measure(&t); + } + + const ecs_type_t *ids = desc->ids; + ecs_entity_t event = desc->event; + ecs_table_t *table = desc->table, *other_table = desc->other_table; + int32_t offset = desc->offset; + int32_t i, r, count = desc->count; + ecs_flags32_t table_flags = table->flags; + + /* Deferring cannot be suspended for observers */ + int32_t defer = world->stages[0].defer; + if (defer < 0) { + world->stages[0].defer *= -1; + } + + /* Table events are emitted for internal table operations only, and do not + * provide component data and/or entity ids. */ + bool table_event = desc->flags & EcsEventTableOnly; + if (!count && !table_event) { + /* If no count is provided, forward event for all entities in table */ + count = ecs_table_count(table) - offset; + } + + /* When the NoOnSet flag is provided, no OnSet/UnSet events should be + * generated when new components are inherited. */ + bool no_on_set = desc->flags & EcsEventNoOnSet; + + /* The world event id is used to determine if an observer has already been + * triggered for an event. Observers for multiple components are split up + * into multiple observers for a single component, and this counter is used + * to make sure a multi observer only triggers once, even if multiple of its + * single-component observers trigger. */ + int32_t evtx = ++world->event_id; + + ecs_id_t ids_cache = 0; + void *ptrs_cache = NULL; + ecs_size_t sizes_cache = 0; + int32_t columns_cache = 0; + ecs_entity_t sources_cache = 0; + + ecs_iter_t it = { + .world = stage, + .real_world = world, + .event = event, + .event_cur = evtx, + .table = table, + .field_count = 1, + .ids = &ids_cache, + .ptrs = &ptrs_cache, + .sizes = &sizes_cache, + .columns = &columns_cache, + .sources = &sources_cache, + .other_table = other_table, + .offset = offset, + .count = count, + .param = ECS_CONST_CAST(void*, desc->param), + .flags = desc->flags | EcsIterIsValid + }; + + ecs_observable_t *observable = ecs_get_observable(desc->observable); + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Event records contain all observers for a specific event. In addition to + * the emitted event, also request data for the Wildcard event (for + * observers subscribing to the wildcard event), OnSet and UnSet events. The + * latter to are used for automatically emitting OnSet/UnSet events for + * inherited components, for example when an IsA relationship is added to an + * entity. This doesn't add much overhead, as fetching records is cheap for + * builtin event types. */ + const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + const ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); + const ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); + const ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet); + + ecs_data_t *storage = NULL; + ecs_column_t *columns = NULL; + if (count) { + storage = &table->data; + columns = storage->columns; + it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset); + } + + int32_t id_count = ids->count; + ecs_id_t *id_array = ids->array; + + /* If a table has IsA relationships, OnAdd/OnRemove events can trigger + * (un)overriding a component. When a component is overridden its value is + * initialized with the value of the overridden component. */ + bool can_override = count && (table_flags & EcsTableHasIsA) && ( + (event == EcsOnAdd) || (event == EcsOnRemove)); + + /* When a new (traversable) relationship is added (emitting an OnAdd/OnRemove + * event) this will cause the components of the target entity to be + * propagated to the source entity. This makes it possible for observers to + * get notified of any new reachable components though the relationship. */ + bool can_forward = event != EcsOnSet; + + /* Does table has observed entities */ + bool has_observed = table_flags & EcsTableHasTraversable; + + /* When a relationship is removed, the events reachable through that + * relationship should emit UnSet events. This is part of the behavior that + * allows observers to be agnostic of whether a component is inherited. */ + bool can_unset = count && (event == EcsOnRemove) && !no_on_set; + + ecs_event_id_record_t *iders[5] = {0}; + int32_t unset_count = 0; + + if (count && can_forward && has_observed) { + flecs_emit_propagate_invalidate(world, table, offset, count); + } + +repeat_event: + /* This is the core event logic, which is executed for each event. By + * default this is just the event kind from the ecs_event_desc_t struct, but + * can also include the Wildcard and UnSet events. The latter is emitted as + * counterpart to OnSet, for any removed ids associated with data. */ + for (i = 0; i < id_count; i ++) { + /* Emit event for each id passed to the function. In most cases this + * will just be one id, like a component that was added, removed or set. + * In some cases events are emitted for multiple ids. + * + * One example is when an id was added with a "With" property, or + * inheriting from a prefab with overrides. In these cases an entity is + * moved directly to the archetype with the additional components. */ + ecs_id_record_t *idr = NULL; + const ecs_type_info_t *ti = NULL; + ecs_id_t id = id_array[i]; + int32_t ider_i, ider_count = 0; + bool is_pair = ECS_IS_PAIR(id); + void *override_ptr = NULL; + ecs_entity_t base = 0; + + /* Check if this id is a pair of an traversable relationship. If so, we + * may have to forward ids from the pair's target. */ + if ((can_forward && is_pair) || can_override) { + idr = flecs_query_id_record_get(world, id); + ecs_flags32_t idr_flags = idr->flags; + + if (is_pair && (idr_flags & EcsIdTraversable)) { + const ecs_event_record_t *er_fwd = NULL; + if (ECS_PAIR_FIRST(id) == EcsIsA) { + if (event == EcsOnAdd) { + if (!world->stages[0].base) { + /* Adding an IsA relationship can trigger prefab + * instantiation, which can instantiate prefab + * hierarchies for the entity to which the + * relationship was added. */ + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + + /* Setting this value prevents flecs_instantiate + * from being called recursively, in case prefab + * children also have IsA relationships. */ + world->stages[0].base = tgt; + flecs_instantiate(world, tgt, table, offset, count); + world->stages[0].base = 0; + } + + /* Adding an IsA relationship will emit OnSet events for + * any new reachable components. */ + er_fwd = er_onset; + } else if (event == EcsOnRemove) { + /* Vice versa for removing an IsA relationship. */ + er_fwd = er_unset; + } + } + + /* Forward events for components from pair target */ + flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + } + + if (can_override && (!(idr_flags & EcsIdDontInherit))) { + /* Initialize overridden components with value from base */ + ti = idr->type_info; + if (ti) { + ecs_table_record_t *base_tr = NULL; + int32_t base_column = ecs_search_relation(world, table, + 0, id, EcsIsA, EcsUp, &base, NULL, &base_tr); + if (base_column != -1) { + /* Base found with component */ + ecs_table_t *base_table = base_tr->hdr.table; + base_column = base_tr->column; + ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *base_r = flecs_entities_get(world, base); + ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t base_row = ECS_RECORD_TO_ROW(base_r->row); + ecs_vec_t *base_v = &base_table->data.columns[base_column].data; + override_ptr = ecs_vec_get(base_v, ti->size, base_row); + } + } + } + } + + if (er) { + /* Get observer sets for id. There can be multiple sets of matching + * observers, in case an observer matches for wildcard ids. For + * example, both observers for (ChildOf, p) and (ChildOf, *) would + * match an event for (ChildOf, p). */ + ider_count = flecs_event_observers_get(er, id, iders); + idr = idr ? idr : flecs_query_id_record_get(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (can_unset) { + /* Increase UnSet count in case this is a component (has data). This + * will cause the event loop to be ran again as UnSet event. */ + idr = idr ? idr : flecs_query_id_record_get(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + unset_count += (idr->type_info != NULL); + } + + if (!ider_count && !override_ptr) { + /* If nothing more to do for this id, early out */ + continue; + } + + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (tr == NULL) { + /* When a single batch contains multiple add's for an exclusive + * relationship, it's possible that an id was in the added list + * that is no longer available for the entity. */ + continue; + } + + int32_t column = tr->index, storage_i; + it.columns[0] = column + 1; + it.ptrs[0] = NULL; + it.sizes[0] = 0; + it.event_id = id; + it.ids[0] = id; + + if (count) { + storage_i = tr->column; + if (storage_i != -1) { + /* If this is a component, fetch pointer & size */ + ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_column_t *c = &columns[storage_i]; + ecs_size_t size = c->ti->size; + void *ptr = ecs_vec_get(&c->data, size, offset); + it.sizes[0] = size; + + if (override_ptr) { + if (event == EcsOnAdd) { + /* If this is a new override, initialize the component + * with the value of the overridden component. */ + flecs_override_copy( + world, table, ti, ptr, override_ptr, offset, count); + } else if (er_onset) { + /* If an override was removed, this re-exposes the + * overridden component. Because this causes the actual + * (now inherited) value of the component to change, an + * OnSet event must be emitted for the base component.*/ + ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); + ecs_event_id_record_t *iders_set[5] = {0}; + int32_t ider_set_i, ider_set_count = + flecs_event_observers_get(er_onset, id, iders_set); + if (ider_set_count) { + /* Set the source temporarily to the base and base + * component pointer. */ + it.sources[0] = base; + it.ptrs[0] = ptr; + for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { + ecs_event_id_record_t *ider = iders_set[ider_set_i]; + flecs_observers_invoke( + world, &ider->self_up, &it, table, EcsIsA); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke( + world, &ider->up, &it, table, EcsIsA); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + } + it.sources[0] = 0; + } + } + } + + it.ptrs[0] = ptr; + } else { + if (it.event == EcsUnSet) { + /* Only valid for components, not tags */ + continue; + } + } + } + + /* Actually invoke observers for this event/id */ + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->self, &it, table, 0); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke(world, &ider->self_up, &it, table, 0); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + } + + if (!ider_count || !count || !has_observed) { + continue; + } + + /* The table->traversable_count value indicates if the table contains any + * entities that are used as targets of traversable relationships. If the + * entity/entities for which the event was generated is used as such a + * target, events must be propagated downwards. */ + ecs_entity_t *entities = it.entities; + it.entities = NULL; + + for (r = 0; r < count; r ++) { + ecs_record_t *record = flecs_entities_get(world, entities[r]); + if (!record) { + /* If the event is emitted after a bulk operation, it's possible + * that it hasn't been populated with entities yet. */ + continue; + } + + ecs_id_record_t *idr_t = record->idr; + if (idr_t) { + /* Entity is used as target in traversable pairs, propagate */ + ecs_entity_t e = entities[r]; + it.sources[0] = e; + flecs_emit_propagate( + world, &it, idr, idr_t, 0, iders, ider_count); + } + } + + it.table = table; + it.other_table = other_table; + it.entities = entities; + it.count = count; + it.offset = offset; + it.sources[0] = 0; + } + + can_override = false; /* Don't override twice */ + can_unset = false; /* Don't unset twice */ + can_forward = false; /* Don't forward twice */ + + if (unset_count && er_unset && (er != er_unset)) { + /* Repeat event loop for UnSet event */ + unset_count = 0; + er = er_unset; + it.event = EcsUnSet; + goto repeat_event; + } + + if (wcer && er != wcer) { + /* Repeat event loop for Wildcard event */ + er = wcer; + it.event = event; + goto repeat_event; + } + +error: + world->stages[0].defer = defer; + + if (measure_time) { + world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); + } + return; +} + +void ecs_emit( + ecs_world_t *stage, + ecs_event_desc_t *desc) +{ + ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, + "cannot set param and const_param at the same time"); + + if (desc->entity) { + ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *r = flecs_entities_get(world, desc->entity); + ecs_table_t *table; + if (!r || !(table = r->table)) { + /* Empty entities can't trigger observers */ + return; + } + desc->table = table; + desc->offset = ECS_RECORD_TO_ROW(r->row); + desc->count = 1; + } + + if (!desc->observable) { + desc->observable = world; + } + + ecs_type_t default_ids = (ecs_type_t){ + .count = 1, + .array = (ecs_id_t[]){ EcsAny } + }; + + if (!desc->ids || !desc->ids->count) { + desc->ids = &default_ids; + } + + if (desc->const_param) { + desc->param = ECS_CONST_CAST(void*, desc->const_param); + desc->const_param = NULL; + } + + flecs_emit(world, stage, desc); + + if (desc->ids == &default_ids) { + desc->ids = NULL; + } +error: + return; +} + +void ecs_enqueue( + ecs_world_t *world, + ecs_event_desc_t *desc) +{ + ecs_check(ecs_is_deferred(world), ECS_INVALID_PARAMETER, + "can't enqueue if not in deferred mode"); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + flecs_enqueue(world, stage, desc); +error: + return; +} + +/** + * @file observer.c + * @brief Observer implementation. + * + * The observer implementation contains functions for creating, deleting and + * invoking observers. The code is split up into single-term observers and + * multi-term observers. Multi-term observers are created from multiple single- + * term observers. + */ + +#include + +static +ecs_entity_t flecs_get_observer_event( + ecs_term_t *term, + ecs_entity_t event) +{ + /* If operator is Not, reverse the event */ + if (term->oper == EcsNot) { + if (event == EcsOnAdd) { + event = EcsOnRemove; + } else if (event == EcsOnRemove) { + event = EcsOnAdd; + } + } + + return event; +} + +static +ecs_flags32_t flecs_id_flag_for_event( + ecs_entity_t e) +{ + if (e == EcsOnAdd) { + return EcsIdHasOnAdd; + } + if (e == EcsOnRemove) { + return EcsIdHasOnRemove; + } + if (e == EcsOnSet) { + return EcsIdHasOnSet; + } + if (e == EcsUnSet) { + return EcsIdHasUnSet; + } + if (e == EcsOnTableFill) { + return EcsIdHasOnTableFill; + } + if (e == EcsOnTableEmpty) { + return EcsIdHasOnTableEmpty; + } + if (e == EcsOnTableCreate) { + return EcsIdHasOnTableCreate; + } + if (e == EcsOnTableDelete) { + return EcsIdHasOnTableDelete; + } + return 0; +} + +static +void flecs_inc_observer_count( + ecs_world_t *world, + ecs_entity_t event, + ecs_event_record_t *evt, + ecs_id_t id, + int32_t value) +{ + ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t result = idt->observer_count += value; + if (result == 1) { + /* Notify framework that there are observers for the event/id. This + * allows parts of the code to skip event evaluation early */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableTriggersForId, + .event = event + }); + + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + idr->flags |= flags; + } + } + } else if (result == 0) { + /* Ditto, but the reverse */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableNoTriggersForId, + .event = event + }); + + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + idr->flags &= ~flags; + } + } + + flecs_event_id_record_remove(evt, id); + ecs_os_free(idt); + } +} + +static +void flecs_register_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + size_t offset) +{ + ecs_id_t term_id = observer->register_id; + ecs_term_t *term = &observer->filter.terms[0]; + ecs_entity_t trav = term->src.trav; + + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, observer->events[i]); + + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_ensure(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get observers for (component) id for event */ + ecs_event_id_record_t *idt = flecs_event_id_record_ensure( + world, er, term_id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *observers = ECS_OFFSET(idt, offset); + ecs_map_init_w_params_if(observers, &world->allocators.ptr); + ecs_map_insert_ptr(observers, observer->filter.entity, observer); + + flecs_inc_observer_count(world, event, er, term_id, 1); + if (trav) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), 1); + } + } +} + +static +void flecs_uni_observer_register( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) +{ + ecs_term_t *term = &observer->filter.terms[0]; + ecs_flags32_t flags = term->src.flags; + + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_register_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self_up)); + } else if (flags & EcsSelf) { + flecs_register_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self)); + } else if (flags & EcsUp) { + ecs_assert(term->src.trav != 0, ECS_INTERNAL_ERROR, NULL); + flecs_register_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, up)); + } +} + +static +void flecs_unregister_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + size_t offset) +{ + ecs_id_t term_id = observer->register_id; + ecs_term_t *term = &observer->filter.terms[0]; + ecs_entity_t trav = term->src.trav; + + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, observer->events[i]); + + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_get(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get observers for (component) id */ + ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *id_observers = ECS_OFFSET(idt, offset); + ecs_map_remove(id_observers, observer->filter.entity); + if (!ecs_map_count(id_observers)) { + ecs_map_fini(id_observers); + } + + flecs_inc_observer_count(world, event, er, term_id, -1); + if (trav) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), -1); + } + } +} + +static +void flecs_unregister_observer( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) +{ + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + if (!observer->filter.terms) { + ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); + return; + } + + ecs_term_t *term = &observer->filter.terms[0]; + ecs_flags32_t flags = term->src.flags; + + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_unregister_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self_up)); + } else if (flags & EcsSelf) { + flecs_unregister_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self)); + } else if (flags & EcsUp) { + flecs_unregister_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, up)); + } +} + +static +bool flecs_ignore_observer( + ecs_observer_t *observer, + ecs_table_t *table, + ecs_iter_t *it) +{ + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t *last_event_id = observer->last_event_id; + if (last_event_id && last_event_id[0] == it->event_cur) { + return true; + } + + ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags; + + bool result = (table_flags & EcsTableIsPrefab) && + !(filter_flags & EcsFilterMatchPrefab); + result = result || ((table_flags & EcsTableIsDisabled) && + !(filter_flags & EcsFilterMatchDisabled)); + + return result; +} + +static +bool flecs_is_simple_result( + ecs_iter_t *it) +{ + return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0); +} + +static +void flecs_observer_invoke( + ecs_world_t *world, + ecs_iter_t *it, + ecs_observer_t *observer, + ecs_iter_action_t callback, + int32_t term_index, + bool simple_result) +{ + ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ecs_should_log_3()) { + char *path = ecs_get_fullpath(world, it->system); + ecs_dbg_3("observer: invoke %s", path); + ecs_os_free(path); + } + + ecs_log_push_3(); + + world->info.observers_ran_frame ++; + + ecs_filter_t *filter = &observer->filter; + ecs_assert(term_index < filter->term_count, ECS_INTERNAL_ERROR, NULL); + ecs_term_t *term = &filter->terms[term_index]; + if (term->oper != EcsNot) { + ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), + ECS_INTERNAL_ERROR, NULL); + } + + bool instanced = filter->flags & EcsFilterIsInstanced; + bool match_this = filter->flags & EcsFilterMatchThis; + bool table_only = it->flags & EcsIterTableOnly; + if (match_this && (simple_result || instanced || table_only)) { + callback(it); + } else { + ecs_entity_t observer_src = term->src.id; + if (observer_src && !(term->src.flags & EcsIsEntity)) { + observer_src = 0; + } + + ecs_entity_t *entities = it->entities; + int32_t i, count = it->count; + ecs_entity_t src = it->sources[0]; + it->count = 1; + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + it->entities = &e; + if (!observer_src) { + callback(it); + } else if (observer_src == e) { + ecs_entity_t dummy = 0; + it->entities = &dummy; + if (!src) { + it->sources[0] = e; + } + callback(it); + it->sources[0] = src; + break; + } + } + it->entities = entities; + it->count = count; + } + + ecs_log_pop_3(); +} + +static +void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + it->ctx = o->ctx; + it->callback = o->callback; + + if (ecs_should_log_3()) { + char *path = ecs_get_fullpath(it->world, it->system); + ecs_dbg_3("observer %s", path); + ecs_os_free(path); + } + + ecs_log_push_3(); + flecs_observer_invoke(it->real_world, it, o, o->callback, 0, + flecs_is_simple_result(it)); + ecs_log_pop_3(); +} + +static +void flecs_uni_observer_invoke( + ecs_world_t *world, + ecs_observer_t *observer, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav, + bool simple_result) +{ + ecs_filter_t *filter = &observer->filter; + ecs_term_t *term = &filter->terms[0]; + if (flecs_ignore_observer(observer, table, it)) { + return; + } + + ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); + if (trav && term->src.trav != trav) { + return; + } + + bool is_filter = term->inout == EcsInOutNone; + ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); + it->system = observer->filter.entity; + it->ctx = observer->ctx; + it->binding_ctx = observer->binding_ctx; + it->term_index = observer->term_index; + it->query = &observer->filter; + it->terms = observer->filter.terms; + + ecs_entity_t event = it->event; + int32_t event_cur = it->event_cur; + it->event = flecs_get_observer_event(term, event); + + if (observer->run) { + it->next = flecs_default_observer_next_callback; + it->callback = flecs_default_uni_observer_run_callback; + it->ctx = observer; + observer->run(it); + } else { + ecs_iter_action_t callback = observer->callback; + it->callback = callback; + flecs_observer_invoke(world, it, observer, callback, 0, simple_result); + } + + it->event = event; + it->event_cur = event_cur; +} + +void flecs_observers_invoke( + ecs_world_t *world, + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav) +{ + if (ecs_map_is_init(observers)) { + ecs_table_lock(it->world, table); + + bool simple_result = flecs_is_simple_result(it); + ecs_map_iter_t oit = ecs_map_iter(observers); + while (ecs_map_next(&oit)) { + ecs_observer_t *o = ecs_map_ptr(&oit); + ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL); + flecs_uni_observer_invoke( + world, o, it, table, trav, simple_result); + } + + ecs_table_unlock(it->world, table); + } +} + +static +bool flecs_multi_observer_invoke( + ecs_iter_t *it) +{ + ecs_observer_t *o = it->ctx; + ecs_world_t *world = it->real_world; + + if (o->last_event_id[0] == it->event_cur) { + /* Already handled this event */ + return false; + } + + o->last_event_id[0] = world->event_id; + + ecs_iter_t user_it = *it; + user_it.field_count = o->filter.field_count; + user_it.query = &o->filter; + user_it.terms = o->filter.terms; + user_it.flags = 0; + ECS_BIT_COND(user_it.flags, EcsIterNoData, + ECS_BIT_IS_SET(o->filter.flags, EcsFilterNoData)); + user_it.ids = NULL; + user_it.columns = NULL; + user_it.sources = NULL; + user_it.sizes = NULL; + user_it.ptrs = NULL; + + flecs_iter_init(it->world, &user_it, flecs_iter_cache_all); + user_it.flags |= (it->flags & EcsIterTableOnly); + + ecs_table_t *table = it->table; + ecs_table_t *prev_table = it->other_table; + int32_t pivot_term = it->term_index; + ecs_term_t *term = &o->filter.terms[pivot_term]; + + int32_t column = it->columns[0]; + if (term->oper == EcsNot) { + table = it->other_table; + prev_table = it->table; + } + + if (!table) { + table = &world->store.root; + } + if (!prev_table) { + prev_table = &world->store.root; + } + + if (column < 0) { + column = -column; + } + + user_it.columns[0] = 0; + user_it.columns[pivot_term] = column; + user_it.sources[pivot_term] = it->sources[0]; + user_it.sizes = o->filter.sizes; + + if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, + user_it.columns, user_it.sources, NULL, NULL, false, pivot_term, + user_it.flags)) + { + /* Monitor observers only invoke when the filter matches for the first + * time with an entity */ + if (o->is_monitor) { + if (flecs_filter_match_table(world, &o->filter, prev_table, + NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) + { + goto done; + } + } + + /* While filter matching needs to be reversed for a Not term, the + * component data must be fetched from the table we got notified for. + * Repeat the matching process for the non-matching table so we get the + * correct column ids and sources, which we need for populate_data */ + if (term->oper == EcsNot) { + flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids, + user_it.columns, user_it.sources, NULL, NULL, false, -1, + user_it.flags | EcsFilterPopulate); + } + + flecs_iter_populate_data(world, &user_it, it->table, it->offset, + it->count, user_it.ptrs); + + user_it.ptrs[pivot_term] = it->ptrs[0]; + user_it.ids[pivot_term] = it->event_id; + user_it.system = o->filter.entity; + user_it.term_index = pivot_term; + user_it.ctx = o->ctx; + user_it.binding_ctx = o->binding_ctx; + user_it.field_count = o->filter.field_count; + user_it.callback = o->callback; + + flecs_iter_validate(&user_it); + ecs_table_lock(it->world, table); + flecs_observer_invoke(world, &user_it, o, o->callback, + pivot_term, flecs_is_simple_result(&user_it)); + ecs_table_unlock(it->world, table); + ecs_iter_fini(&user_it); + return true; + } + +done: + ecs_iter_fini(&user_it); + return false; +} + +bool ecs_observer_default_run_action(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + if (o->is_multi) { + return flecs_multi_observer_invoke(it); + } else { + it->ctx = o->ctx; + ecs_table_lock(it->world, it->table); + flecs_observer_invoke(it->real_world, it, o, o->callback, 0, + flecs_is_simple_result(it)); + ecs_table_unlock(it->world, it->table); + return true; + } +} + +static +void flecs_default_multi_observer_run_callback( + ecs_iter_t *it) +{ + flecs_multi_observer_invoke(it); +} + +/* For convenience, so applications can (in theory) use a single run callback + * that uses ecs_iter_next to iterate results */ +bool flecs_default_observer_next_callback(ecs_iter_t *it) { + if (it->interrupted_by) { + return false; + } else { + /* Use interrupted_by to signal the next iteration must return false */ + it->interrupted_by = it->system; + return true; + } +} + +/* Run action for children of multi observer */ +static +void flecs_multi_observer_builtin_run(ecs_iter_t *it) { + ecs_observer_t *observer = it->ctx; + ecs_run_action_t run = observer->run; + + if (run) { + it->next = flecs_default_observer_next_callback; + it->callback = flecs_default_multi_observer_run_callback; + it->interrupted_by = 0; + run(it); + } else { + flecs_multi_observer_invoke(it); + } +} + +static +void flecs_uni_observer_yield_existing( + ecs_world_t *world, + ecs_observer_t *observer) +{ + ecs_iter_action_t callback = observer->callback; + + ecs_defer_begin(world); + + /* If yield existing is enabled, observer for each thing that matches + * the event, if the event is iterable. */ + int i, count = observer->event_count; + for (i = 0; i < count; i ++) { + ecs_entity_t evt = observer->events[i]; + const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); + if (!iterable) { + continue; + } + + ecs_iter_t it; + iterable->init(world, world, &it, &observer->filter.terms[0]); + it.system = observer->filter.entity; + it.ctx = observer->ctx; + it.binding_ctx = observer->binding_ctx; + it.event = evt; + + ecs_iter_next_action_t next = it.next; + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + while (next(&it)) { + it.event_id = it.ids[0]; + it.event_cur = ++ world->event_id; + callback(&it); + } + } + + ecs_defer_end(world); +} + +static +void flecs_multi_observer_yield_existing( + ecs_world_t *world, + ecs_observer_t *observer) +{ + ecs_run_action_t run = observer->run; + if (!run) { + run = flecs_default_multi_observer_run_callback; + } + + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + ecs_defer_begin(world); + + int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); + if (pivot_term < 0) { + return; + } + + /* If yield existing is enabled, invoke for each thing that matches + * the event, if the event is iterable. */ + int i, count = observer->event_count; + for (i = 0; i < count; i ++) { + ecs_entity_t evt = observer->events[i]; + const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); + if (!iterable) { + continue; + } + + ecs_iter_t it; + iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); + it.query = &observer->filter; + it.terms = observer->filter.terms; + it.field_count = 1; + it.term_index = pivot_term; + it.system = observer->filter.entity; + it.ctx = observer; + it.binding_ctx = observer->binding_ctx; + it.event = evt; + + ecs_iter_next_action_t next = it.next; + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + while (next(&it)) { + it.event_id = it.ids[0]; + it.event_cur = ++ world->event_id; + run(&it); + } + } + + ecs_defer_end(world); +} + +static +int flecs_uni_observer_init( + ecs_world_t *world, + ecs_observer_t *observer, + const ecs_observer_desc_t *desc) +{ + ecs_term_t *term = &observer->filter.terms[0]; + observer->last_event_id = desc->last_event_id; + if (!observer->last_event_id) { + observer->last_event_id = &observer->last_event_id_storage; + } + observer->register_id = flecs_from_public_id(world, term->id); + term->field_index = desc->term_index; + + if (ecs_id_is_tag(world, term->id)) { + /* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */ + int32_t e, count = observer->event_count; + for (e = 0; e < count; e ++) { + if (observer->events[e] == EcsOnSet) { + observer->events[e] = EcsOnAdd; + } else + if (observer->events[e] == EcsUnSet) { + observer->events[e] = EcsOnRemove; + } + } + } + + flecs_uni_observer_register(world, observer->observable, observer); + + if (desc->yield_existing) { + flecs_uni_observer_yield_existing(world, observer); + } + + return 0; +} + +static +int flecs_multi_observer_init( + ecs_world_t *world, + ecs_observer_t *observer, + const ecs_observer_desc_t *desc) +{ + /* Create last event id for filtering out the same event that arrives from + * more than one term */ + observer->last_event_id = ecs_os_calloc_t(int32_t); + + /* Mark observer as multi observer */ + observer->is_multi = true; + + /* Create a child observer for each term in the filter */ + ecs_filter_t *filter = &observer->filter; + ecs_observer_desc_t child_desc = *desc; + child_desc.last_event_id = observer->last_event_id; + child_desc.run = NULL; + child_desc.callback = flecs_multi_observer_builtin_run; + child_desc.ctx = observer; + child_desc.ctx_free = NULL; + child_desc.filter.expr = NULL; + child_desc.filter.terms_buffer = NULL; + child_desc.filter.terms_buffer_count = 0; + child_desc.binding_ctx = NULL; + child_desc.binding_ctx_free = NULL; + child_desc.yield_existing = false; + ecs_os_zeromem(&child_desc.entity); + ecs_os_zeromem(&child_desc.filter.terms); + ecs_os_memcpy_n(child_desc.events, observer->events, + ecs_entity_t, observer->event_count); + + int i, term_count = filter->term_count; + bool optional_only = filter->flags & EcsFilterMatchThis; + for (i = 0; i < term_count; i ++) { + if (filter->terms[i].oper != EcsOptional) { + if (ecs_term_match_this(&filter->terms[i])) { + optional_only = false; + } + } + } + + if (filter->flags & EcsFilterMatchPrefab) { + child_desc.filter.flags |= EcsFilterMatchPrefab; + } + if (filter->flags & EcsFilterMatchDisabled) { + child_desc.filter.flags |= EcsFilterMatchDisabled; + } + + /* Create observers as children of observer */ + ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity); + + for (i = 0; i < term_count; i ++) { + if (filter->terms[i].src.flags & EcsFilter) { + continue; + } + + ecs_term_t *term = &child_desc.filter.terms[0]; + child_desc.term_index = filter->terms[i].field_index; + *term = filter->terms[i]; + + ecs_oper_kind_t oper = term->oper; + ecs_id_t id = term->id; + + /* AndFrom & OrFrom terms insert multiple observers */ + if (oper == EcsAndFrom || oper == EcsOrFrom) { + const ecs_type_t *type = ecs_get_type(world, id); + int32_t ti, ti_count = type->count; + ecs_id_t *ti_ids = type->array; + + /* Correct operator will be applied when an event occurs, and + * the observer is evaluated on the observer source */ + term->oper = EcsAnd; + for (ti = 0; ti < ti_count; ti ++) { + ecs_id_t ti_id = ti_ids[ti]; + ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); + if (idr->flags & EcsIdDontInherit) { + continue; + } + + term->first.name = NULL; + term->first.id = ti_ids[ti]; + term->id = ti_ids[ti]; + + if (ecs_observer_init(world, &child_desc) == 0) { + goto error; + } + } + continue; + } + + /* Single component observers never use OR */ + if (oper == EcsOr) { + term->oper = EcsAnd; + } + + /* If observer only contains optional terms, match everything */ + if (optional_only) { + term->id = EcsAny; + term->first.id = EcsAny; + term->src.id = EcsThis; + term->src.flags = EcsIsVariable; + term->second.id = 0; + } else if (term->oper == EcsOptional) { + continue; + } + if (ecs_observer_init(world, &child_desc) == 0) { + goto error; + } + + if (optional_only) { + break; + } + } + + ecs_set_scope(world, old_scope); + + if (desc->yield_existing) { + flecs_multi_observer_yield_existing(world, observer); + } + + return 0; +error: + return -1; +} + +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); + + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_new(world, 0); + } + + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_observer_t); + if (!poly->poly) { + ecs_check(desc->callback != NULL || desc->run != NULL, + ECS_INVALID_OPERATION, NULL); + + ecs_observer_t *observer = ecs_poly_new(ecs_observer_t); + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini; + + /* Make writeable copy of filter desc so that we can set name. This will + * make debugging easier, as any error messages related to creating the + * filter will have the name of the observer. */ + ecs_filter_desc_t filter_desc = desc->filter; + filter_desc.entity = entity; + ecs_filter_t *filter = filter_desc.storage = &observer->filter; + *filter = ECS_FILTER_INIT; + + /* Parse filter */ + if (ecs_filter_init(world, &filter_desc) == NULL) { + flecs_observer_fini(observer); + return 0; + } + + /* Observer must have at least one term */ + ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL); + + poly->poly = observer; + + ecs_observable_t *observable = desc->observable; + if (!observable) { + observable = ecs_get_observable(world); + } + + observer->run = desc->run; + observer->callback = desc->callback; + observer->ctx = desc->ctx; + observer->binding_ctx = desc->binding_ctx; + observer->ctx_free = desc->ctx_free; + observer->binding_ctx_free = desc->binding_ctx_free; + observer->term_index = desc->term_index; + observer->observable = observable; + + /* Check if observer is monitor. Monitors are created as multi observers + * since they require pre/post checking of the filter to test if the + * entity is entering/leaving the monitor. */ + int i; + for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) { + ecs_entity_t event = desc->events[i]; + if (!event) { + break; + } + + if (event == EcsMonitor) { + /* Monitor event must be first and last event */ + ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); + + observer->events[0] = EcsOnAdd; + observer->events[1] = EcsOnRemove; + observer->event_count ++; + observer->is_monitor = true; + } else { + observer->events[i] = event; + } + + observer->event_count ++; + } + + /* Observer must have at least one event */ + ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); + + bool multi = false; + + if (filter->term_count == 1 && !desc->last_event_id) { + ecs_term_t *term = &filter->terms[0]; + /* If the filter has a single term but it is a *From operator, we + * need to create a multi observer */ + multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); + + /* An observer with only optional terms is a special case that is + * only handled by multi observers */ + multi |= term->oper == EcsOptional; + } + + if (filter->term_count == 1 && !observer->is_monitor && !multi) { + if (flecs_uni_observer_init(world, observer, desc)) { + goto error; + } + } else { + if (flecs_multi_observer_init(world, observer, desc)) { + goto error; + } + } + + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]observer#[reset] %s created", + ecs_get_name(world, entity)); + } + } else { + ecs_poly_assert(poly->poly, ecs_observer_t); + ecs_observer_t *observer = (ecs_observer_t*)poly->poly; + + if (desc->run) { + observer->run = desc->run; + } + if (desc->callback) { + observer->callback = desc->callback; + } + + if (observer->ctx_free) { + if (observer->ctx && observer->ctx != desc->ctx) { + observer->ctx_free(observer->ctx); + } + } + if (observer->binding_ctx_free) { + if (observer->binding_ctx && observer->binding_ctx != desc->binding_ctx) { + observer->binding_ctx_free(observer->binding_ctx); + } + } + + if (desc->ctx) { + observer->ctx = desc->ctx; + } + if (desc->binding_ctx) { + observer->binding_ctx = desc->binding_ctx; + } + if (desc->ctx_free) { + observer->ctx_free = desc->ctx_free; + } + if (desc->binding_ctx_free) { + observer->binding_ctx_free = desc->binding_ctx_free; + } + } + + ecs_poly_modified(world, entity, ecs_observer_t); + + return entity; +error: + ecs_delete(world, entity); + return 0; +} + +void* ecs_observer_get_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); + if (o) { + ecs_poly_assert(o->poly, ecs_observer_t); + return ((ecs_observer_t*)o->poly)->ctx; + } else { + return NULL; + } +} + +void* ecs_observer_get_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); + if (o) { + ecs_poly_assert(o->poly, ecs_observer_t); + return ((ecs_observer_t*)o->poly)->binding_ctx; + } else { + return NULL; + } +} + +void flecs_observer_fini( + ecs_observer_t *observer) +{ + if (observer->is_multi) { + ecs_os_free(observer->last_event_id); + } else { + if (observer->filter.term_count) { + flecs_unregister_observer( + observer->filter.world, observer->observable, observer); + } else { + /* Observer creation failed while creating filter */ + } + } + + /* Cleanup filters */ + ecs_filter_fini(&observer->filter); + + /* Cleanup context */ + if (observer->ctx_free) { + observer->ctx_free(observer->ctx); + } + + if (observer->binding_ctx_free) { + observer->binding_ctx_free(observer->binding_ctx); + } + + ecs_poly_free(observer, ecs_observer_t); +} + +/** + * @file os_api.c + * @brief Operating system abstraction API. + * + * The OS API implements an overridable interface for implementing functions + * that are operating system specific, in addition to a number of hooks which + * allow for customization by the user, like logging. + */ + +#include +#include + +void ecs_os_api_impl(ecs_os_api_t *api); + +static bool ecs_os_api_initialized = false; +static bool ecs_os_api_initializing = false; +static int ecs_os_api_init_count = 0; + +ecs_os_api_t ecs_os_api = { + .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, + .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +}; + +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; + } +} + +ecs_os_api_t ecs_os_get_api(void) { + return ecs_os_api; +} + +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_(); + } + } +} + +/* Assume every non-glibc Linux target has no execinfo. + This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ +#if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__) +#define HAVE_EXECINFO 0 +#elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) +#define HAVE_EXECINFO 1 +#else +#define HAVE_EXECINFO 0 +#endif + +#if HAVE_EXECINFO +#include +#define ECS_BT_BUF_SIZE 100 + +void flecs_dump_backtrace( + void *stream) +{ + int nptrs; + void *buffer[ECS_BT_BUF_SIZE]; + char **strings; + + nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); + + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + return; + } + + for (int j = 1; j < nptrs; j++) { + fprintf(stream, "%s\n", strings[j]); + } + + free(strings); +} +#else +void flecs_dump_backtrace( + void *stream) +{ + (void)stream; +} +#endif +#undef HAVE_EXECINFO_H + +static +void flecs_log_msg( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + FILE *stream = ecs_os_api.log_out_; + if (!stream) { + stream = stdout; + } + + bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; + bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; + + time_t now = 0; + + if (deltatime) { + now = time(NULL); + int64_t delta = 0; + if (ecs_os_api.log_last_timestamp_) { + delta = now - ecs_os_api.log_last_timestamp_; + } + ecs_os_api.log_last_timestamp_ = (int64_t)now; + + if (delta) { + if (delta < 10) { + fputs(" ", stream); + } + if (delta < 100) { + fputs(" ", stream); + } + char time_buf[20]; + ecs_os_sprintf(time_buf, "%u", (uint32_t)delta); + fputs("+", stream); + fputs(time_buf, stream); + fputs(" ", stream); + } else { + fputs(" ", stream); + } + } + + if (timestamp) { + if (!now) { + now = time(NULL); + } + char time_buf[20]; + ecs_os_sprintf(time_buf, "%u", (uint32_t)now); + fputs(time_buf, stream); + fputs(" ", stream); + } + + if (level >= 4) { + if (use_colors) fputs(ECS_NORMAL, stream); + fputs("jrnl", stream); + } else if (level >= 0) { + if (level == 0) { + if (use_colors) fputs(ECS_MAGENTA, stream); + } else { + if (use_colors) fputs(ECS_GREY, stream); + } + fputs("info", stream); + } else if (level == -2) { + if (use_colors) fputs(ECS_YELLOW, stream); + fputs("warning", stream); + } else if (level == -3) { + if (use_colors) fputs(ECS_RED, stream); + fputs("error", stream); + } else if (level == -4) { + if (use_colors) fputs(ECS_RED, stream); + fputs("fatal", stream); + } + + if (use_colors) fputs(ECS_NORMAL, stream); + fputs(": ", stream); + + if (level >= 0) { + if (ecs_os_api.log_indent_) { + char indent[32]; + int i, indent_count = ecs_os_api.log_indent_; + if (indent_count > 15) indent_count = 15; + + for (i = 0; i < indent_count; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; + } + + if (ecs_os_api.log_indent_ != indent_count) { + indent[i * 2 - 2] = '+'; + } + + indent[i * 2] = '\0'; + + fputs(indent, stream); + } + } + + if (level < 0) { + if (file) { + const char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); + } + + if (file_ptr) { + file = file_ptr + 1; + } + + fputs(file, stream); + fputs(": ", stream); + } + + if (line) { + fprintf(stream, "%d: ", line); + } + } + + fputs(msg, stream); + + fputs("\n", stream); + + if (level == -4) { + flecs_dump_backtrace(stream); + } +} + +void ecs_os_dbg( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(1, file, line, msg); + } +} + +void ecs_os_trace( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(0, file, line, msg); + } +} + +void ecs_os_warn( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-2, file, line, msg); + } +} + +void ecs_os_err( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-3, file, line, msg); + } +} + +void ecs_os_fatal( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-4, file, line, msg); + } +} + +static +void ecs_os_gettime(ecs_time_t *time) { + ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); + + uint64_t now = ecs_os_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_linc(&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_linc(&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_linc(&ecs_os_api_realloc_count); + } else { + /* If not actually reallocing, treat as malloc */ + ecs_os_linc(&ecs_os_api_malloc_count); + } + + return realloc(ptr, (size_t)size); +} + +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + ecs_os_linc(&ecs_os_api_free_count); + } + free(ptr); +} + +static +char* ecs_os_api_strdup(const char *str) { + if (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; + } else { + return NULL; + } +} + +void ecs_os_strset(char **str, const char *value) { + char *old = str[0]; + str[0] = ecs_os_strdup(value); + ecs_os_free(old); +} + +/* 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_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) + ecs_strbuf_appendlit(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, ".so"); +# elif defined(ECS_TARGET_DARWIN) + ecs_strbuf_appendlit(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, ".dylib"); +# elif defined(ECS_TARGET_WINDOWS) + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&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_appendlit(&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; + } + + if (ecs_os_api_initializing != 0) { + return; + } + + ecs_os_api_initializing = true; + + /* 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.get_time_ = ecs_os_gettime; + + /* Logging */ + ecs_os_api.log_ = flecs_log_msg; + + /* 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; + +# ifdef FLECS_OS_API_IMPL + /* Initialize defaults to OS API IMPL addon, but still allow for overriding + * by the application */ + ecs_set_os_api_impl(); + ecs_os_api_initialized = false; +# endif + + ecs_os_api_initializing = false; +} + +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) && + (ecs_os_api.thread_self_ != NULL); +} + +bool ecs_os_has_task_support(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.task_new_ != NULL) && + (ecs_os_api.task_join_ != NULL); +} + +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL) && + (ecs_os_api.now_ != NULL); +} + +bool ecs_os_has_logging(void) { + return (ecs_os_api.log_ != 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); +} + +#if defined(ECS_TARGET_WINDOWS) +static char error_str[255]; +#endif + +const char* ecs_os_strerror(int err) { +# if defined(ECS_TARGET_WINDOWS) + strerror_s(error_str, 255, err); + return error_str; +# else + return strerror(err); +# endif +} + +/** + * @file poly.c + * @brief Functions for managing poly objects. + * + * The poly framework makes it possible to generalize common functionality for + * different kinds of API objects, as well as improved type safety checks. Poly + * objects have a header that identifiers what kind of object it is. This can + * then be used to discover a set of "mixins" implemented by the type. + * + * Mixins are like a vtable, but for members. Each type populates the table with + * offsets to the members that correspond with the mixin. If an entry in the + * mixin table is not set, the type does not support the mixin. + * + * An example is the Iterable mixin, which makes it possible to create an + * iterator for any poly object (like filters, queries, the world) that + * implements the Iterable mixin. + */ + + +static const char* mixin_kind_str[] = { + [EcsMixinWorld] = "world", + [EcsMixinEntity] = "entity", + [EcsMixinObservable] = "observable", + [EcsMixinIterable] = "iterable", + [EcsMixinDtor] = "dtor", + [EcsMixinMax] = "max (should never be requested by application)" +}; + +ecs_mixins_t ecs_world_t_mixins = { + .type_name = "ecs_world_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_world_t, self), + [EcsMixinObservable] = offsetof(ecs_world_t, observable), + [EcsMixinIterable] = offsetof(ecs_world_t, iterable) + } +}; + +ecs_mixins_t ecs_stage_t_mixins = { + .type_name = "ecs_stage_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_stage_t, world) + } +}; + +ecs_mixins_t ecs_query_t_mixins = { + .type_name = "ecs_query_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_query_t, filter.world), + [EcsMixinEntity] = offsetof(ecs_query_t, filter.entity), + [EcsMixinIterable] = offsetof(ecs_query_t, iterable), + [EcsMixinDtor] = offsetof(ecs_query_t, dtor) + } +}; + +ecs_mixins_t ecs_observer_t_mixins = { + .type_name = "ecs_observer_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_observer_t, filter.world), + [EcsMixinEntity] = offsetof(ecs_observer_t, filter.entity), + [EcsMixinDtor] = offsetof(ecs_observer_t, dtor) + } +}; + +ecs_mixins_t ecs_filter_t_mixins = { + .type_name = "ecs_filter_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_filter_t, world), + [EcsMixinEntity] = offsetof(ecs_filter_t, entity), + [EcsMixinIterable] = offsetof(ecs_filter_t, iterable), + [EcsMixinDtor] = offsetof(ecs_filter_t, dtor) + } +}; + +static +void* assert_mixin( + const ecs_poly_t *poly, + ecs_mixin_kind_t kind) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); + + const ecs_header_t *hdr = poly; + ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + + const ecs_mixins_t *mixins = hdr->mixins; + ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t offset = mixins->elems[kind]; + ecs_assert(offset != 0, ECS_INVALID_PARAMETER, + "mixin %s not available for type %s", + mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); + (void)mixin_kind_str; + + /* Object has mixin, return its address */ + return ECS_OFFSET(hdr, offset); +} + +void* ecs_poly_init_( + ecs_poly_t *poly, + int32_t type, + ecs_size_t size, + ecs_mixins_t *mixins) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_header_t *hdr = poly; + ecs_os_memset(poly, 0, size); + + hdr->magic = ECS_OBJECT_MAGIC; + hdr->type = type; + hdr->mixins = mixins; + + return poly; +} + +void ecs_poly_fini_( + ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + (void)type; + + ecs_header_t *hdr = poly; + + /* Don't deinit poly that wasn't initialized */ + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); + hdr->magic = 0; +} + +EcsPoly* ecs_poly_bind_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + /* Add tag to the entity for easy querying. This will make it possible to + * query for `Query` instead of `(Poly, Query) */ + if (!ecs_has_id(world, entity, tag)) { + ecs_add_id(world, entity, tag); + } + + /* Never defer creation of a poly object */ + bool deferred = false; + if (ecs_is_deferred(world)) { + deferred = true; + ecs_defer_suspend(world); + } + + /* If this is a new poly, leave the actual creation up to the caller so they + * call tell the difference between a create or an update */ + EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag); + + if (deferred) { + ecs_defer_resume(world); + } + + return result; +} + +void ecs_poly_modified_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); +} + +const EcsPoly* ecs_poly_bind_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + return ecs_get_pair(world, entity, EcsPoly, tag); +} + +ecs_poly_t* ecs_poly_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + const EcsPoly *p = ecs_poly_bind_get_(world, entity, tag); + if (p) { + return p->poly; + } + return NULL; +} + +bool ecs_poly_is_( + const ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + return hdr->type == type; +} + +ecs_iterable_t* ecs_get_iterable( + const ecs_poly_t *poly) +{ + return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); +} + +ecs_observable_t* ecs_get_observable( + const ecs_poly_t *poly) +{ + return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); +} + +const ecs_world_t* ecs_get_world( + const ecs_poly_t *poly) +{ + if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) { + return poly; + } + return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); +} + +ecs_entity_t ecs_get_entity( + const ecs_poly_t *poly) +{ + return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); +} + +ecs_poly_dtor_t* ecs_get_dtor( + const ecs_poly_t *poly) +{ + return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); +} + +/** + * @file query.c + * @brief Cached query implementation. + * + * Cached queries store a list of matched tables. The inputs for a cached query + * are a filter and an observer. The filter is used to initially populate the + * cache, and an observer is used to keep the cacne up to date. + * + * Cached queries additionally support features like sorting and grouping. + * With sorting, an application can iterate over entities that can be sorted by + * a component. Grouping allows an application to group matched tables, which is + * used internally to implement the cascade feature, and can additionally be + * used to implement things like world cells. + */ + + +static +uint64_t flecs_query_get_group_id( + ecs_query_t *query, + ecs_table_t *table) +{ + if (query->group_by) { + return query->group_by(query->filter.world, table, + query->group_by_id, query->group_by_ctx); + } else { + return 0; + } +} + +static +void flecs_query_compute_group_id( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + + if (query->group_by) { + ecs_table_t *table = match->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + match->group_id = flecs_query_get_group_id(query, table); + } else { + match->group_id = 0; + } +} + +static +ecs_query_table_list_t* flecs_query_get_group( + const ecs_query_t *query, + uint64_t group_id) +{ + return ecs_map_get_deref(&query->groups, ecs_query_table_list_t, group_id); +} + +static +ecs_query_table_list_t* flecs_query_ensure_group( + ecs_query_t *query, + uint64_t id) +{ + ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, + ecs_query_table_list_t, id); + + if (!group) { + group = ecs_map_insert_alloc_t(&query->groups, + ecs_query_table_list_t, id); + ecs_os_zeromem(group); + if (query->on_group_create) { + group->info.ctx = query->on_group_create( + query->filter.world, id, query->group_by_ctx); + } + } + + return group; +} + +static +void flecs_query_remove_group( + ecs_query_t *query, + uint64_t id) +{ + if (query->on_group_delete) { + ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, + ecs_query_table_list_t, id); + if (group) { + query->on_group_delete(query->filter.world, id, + group->info.ctx, query->group_by_ctx); + } + } + + ecs_map_remove_free(&query->groups, id); +} + +static +uint64_t flecs_query_default_group_by( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) +{ + (void)ctx; + + ecs_id_t match; + if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { + return ecs_pair_second(world, match); + } + return 0; +} + +/* Find the last node of the group after which this group should be inserted */ +static +ecs_query_table_match_t* flecs_query_find_group_insertion_node( + ecs_query_t *query, + uint64_t group_id) +{ + /* Grouping must be enabled */ + ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_iter_t it = ecs_map_iter(&query->groups); + ecs_query_table_list_t *list, *closest_list = NULL; + uint64_t id, closest_id = 0; + + bool desc = false; + + if (query->cascade_by) { + desc = (query->filter.terms[ + query->cascade_by - 1].src.flags & EcsDesc) != 0; + } + + /* Find closest smaller group id */ + while (ecs_map_next(&it)) { + id = ecs_map_key(&it); + + if (!desc) { + if (id >= group_id) { + continue; + } + } else { + if (id <= group_id) { + continue; + } + } + + list = ecs_map_ptr(&it); + if (!list->last) { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } + + bool comp; + if (!desc) { + comp = ((group_id - id) < (group_id - closest_id)); + } else { + comp = ((group_id - id) > (group_id - closest_id)); + } + + if (!closest_list || comp) { + closest_id = id; + closest_list = list; + } + } + + if (closest_list) { + return closest_list->last; + } else { + return NULL; /* Group should be first in query */ + } +} + +/* Initialize group with first node */ +static +void flecs_query_create_group( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + uint64_t group_id = match->group_id; + + /* If query has grouping enabled & this is a new/empty group, find + * the insertion point for the group */ + ecs_query_table_match_t *insert_after = flecs_query_find_group_insertion_node( + query, group_id); + + if (!insert_after) { + /* This group should appear first in the query list */ + ecs_query_table_match_t *query_first = query->list.first; + if (query_first) { + /* If this is not the first match for the query, insert before it */ + match->next = query_first; + query_first->prev = match; + query->list.first = match; + } else { + /* If this is the first match of the query, initialize its list */ + ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL); + query->list.first = match; + query->list.last = match; + } + } else { + ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This group should appear after another group */ + ecs_query_table_match_t *insert_before = insert_after->next; + match->prev = insert_after; + insert_after->next = match; + match->next = insert_before; + if (insert_before) { + insert_before->prev = match; + } else { + ecs_assert(query->list.last == insert_after, + ECS_INTERNAL_ERROR, NULL); + + /* This group should appear last in the query list */ + query->list.last = match; + } + } +} + +/* Find the list the node should be part of */ +static +ecs_query_table_list_t* flecs_query_get_node_list( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + if (query->group_by) { + return flecs_query_get_group(query, match->group_id); + } else { + return &query->list; + } +} + +/* Find or create the list the node should be part of */ +static +ecs_query_table_list_t* flecs_query_ensure_node_list( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + if (query->group_by) { + return flecs_query_ensure_group(query, match->group_id); + } else { + return &query->list; + } +} + +/* Remove node from list */ +static +void flecs_query_remove_table_node( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + ecs_query_table_match_t *prev = match->prev; + ecs_query_table_match_t *next = match->next; + + ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); + + ecs_query_table_list_t *list = flecs_query_get_node_list(query, match); + + if (!list || !list->first) { + /* If list contains no matches, the match must be empty */ + ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + return; + } + + ecs_assert(prev != NULL || query->list.first == match, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != NULL || query->list.last == match, + ECS_INTERNAL_ERROR, NULL); + + if (prev) { + prev->next = next; + } + if (next) { + next->prev = prev; + } + + ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + list->info.table_count --; + + if (query->group_by) { + uint64_t group_id = match->group_id; + + /* Make sure query.list is updated if this is the first or last group */ + if (query->list.first == match) { + ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); + query->list.first = next; + prev = next; + } + if (query->list.last == match) { + ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); + query->list.last = prev; + next = prev; + } + + ecs_assert(query->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + query->list.info.table_count --; + list->info.match_count ++; + + /* Make sure group list only contains nodes that belong to the group */ + if (prev && prev->group_id != group_id) { + /* The previous node belonged to another group */ + prev = next; + } + if (next && next->group_id != group_id) { + /* The next node belonged to another group */ + next = prev; + } + + /* Do check again, in case both prev & next belonged to another group */ + if ((!prev && !next) || (prev && prev->group_id != group_id)) { + /* There are no more matches left in this group */ + flecs_query_remove_group(query, group_id); + list = NULL; + } + } + + if (list) { + if (list->first == match) { + list->first = next; + } + if (list->last == match) { + list->last = prev; + } + } + + match->prev = NULL; + match->next = NULL; + + query->match_count ++; +} + +/* Add node to list */ +static +void flecs_query_insert_table_node( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + /* Node should not be part of an existing list */ + ecs_assert(match->prev == NULL && match->next == NULL, + ECS_INTERNAL_ERROR, NULL); + + /* If this is the first match, activate system */ + if (!query->list.first && query->filter.entity) { + ecs_remove_id(query->filter.world, query->filter.entity, EcsEmpty); + } + + flecs_query_compute_group_id(query, match); + + ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, match); + if (list->last) { + ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_query_table_match_t *last = list->last; + ecs_query_table_match_t *last_next = last->next; + + match->prev = last; + match->next = last_next; + last->next = match; + + if (last_next) { + last_next->prev = match; + } + + list->last = match; + + if (query->group_by) { + /* Make sure to update query list if this is the last group */ + if (query->list.last == last) { + query->list.last = match; + } + } + } else { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + + list->first = match; + list->last = match; + + if (query->group_by) { + /* Initialize group with its first node */ + flecs_query_create_group(query, match); + } + } + + if (query->group_by) { + list->info.table_count ++; + list->info.match_count ++; + } + + query->list.info.table_count ++; + query->match_count ++; + + ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); +} + +static +ecs_query_table_match_t* flecs_query_cache_add( + ecs_world_t *world, + ecs_query_table_t *elem) +{ + ecs_query_table_match_t *result = + flecs_bcalloc(&world->allocators.query_table_match); + + if (!elem->first) { + elem->first = result; + elem->last = result; + } else { + ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); + elem->last->next_match = result; + elem->last = result; + } + + return result; +} + +typedef struct { + ecs_table_t *table; + int32_t column; +} flecs_table_column_t; + +static +void flecs_query_get_column_for_term( + ecs_query_t *query, + ecs_query_table_match_t *match, + int32_t t, + flecs_table_column_t *out) +{ + const ecs_filter_t *filter = &query->filter; + ecs_world_t *world = filter->world; + ecs_term_t *term = &filter->terms[t]; + int32_t field = term->field_index; + ecs_entity_t src = match->sources[field]; + ecs_table_t *table = NULL; + int32_t column = -1; + + if (term->oper != EcsNot) { + if (!src) { + if (term->src.flags != EcsIsEntity) { + table = match->table; + column = match->storage_columns[field]; + if (column == -2) { + /* Shared field */ + column = -1; + } + } + } else { + table = ecs_get_table(world, src); + if (ecs_term_match_this(term)) { + int32_t ref_index = -match->columns[field] - 1; + ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); + if (ref->id != 0) { + ecs_ref_update(world, ref); + column = ref->tr->column; + } + } else { + column = -(match->columns[field] + 1); + } + } + } + + out->table = table; + out->column = column; +} + +/* Get match monitor. Monitors are used to keep track of whether components + * matched by the query in a table have changed. */ +static +bool flecs_query_get_match_monitor( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + if (match->monitor) { + return false; + } + + int32_t *monitor = flecs_balloc(&query->allocators.monitors); + monitor[0] = 0; + + /* Mark terms that don't need to be monitored. This saves time when reading + * and/or updating the monitor. */ + const ecs_filter_t *f = &query->filter; + int32_t i, field = -1, term_count = f->term_count; + flecs_table_column_t tc; + + for (i = 0; i < term_count; i ++) { + if (field == f->terms[i].field_index) { + if (monitor[field + 1] != -1) { + continue; + } + } + + field = f->terms[i].field_index; + monitor[field + 1] = -1; + + if (f->terms[i].inout != EcsIn && + f->terms[i].inout != EcsInOut && + f->terms[i].inout != EcsInOutDefault) { + continue; /* If term isn't read, don't monitor */ + } + + int32_t column = match->columns[field]; + if (column == 0) { + continue; /* Don't track terms that aren't matched */ + } + + flecs_query_get_column_for_term(query, match, i, &tc); + if (tc.column == -1) { + continue; /* Don't track terms that aren't stored */ + } + + monitor[field + 1] = 0; + } + + /* If matched table needs entity filter, make sure to test fields that could + * be matched by flattened parents. */ + ecs_entity_filter_t *ef = match->entity_filter; + if (ef && ef->flat_tree_column != -1) { + int32_t *fields = ecs_vec_first(&ef->ft_terms); + int32_t field_count = ecs_vec_count(&ef->ft_terms); + for (i = 0; i < field_count; i ++) { + monitor[fields[i] + 1] = 0; + } + } + + match->monitor = monitor; + + query->flags |= EcsQueryHasMonitor; + + return true; +} + +/* Synchronize match monitor with table dirty state */ +static +void flecs_query_sync_match_monitor( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + if (!match->monitor) { + if (query->flags & EcsQueryHasMonitor) { + flecs_query_get_match_monitor(query, match); + } else { + return; + } + } + + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + query->filter.world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + } + + ecs_filter_t *filter = &query->filter; + { + flecs_table_column_t tc; + int32_t t, term_count = filter->term_count; + for (t = 0; t < term_count; t ++) { + int32_t field = filter->terms[t].field_index; + if (monitor[field + 1] == -1) { + continue; + } + + flecs_query_get_column_for_term(query, match, t, &tc); + + monitor[field + 1] = flecs_table_get_dirty_state( + filter->world, tc.table)[tc.column + 1]; + } + } + + ecs_entity_filter_t *ef = match->entity_filter; + if (ef && ef->flat_tree_column != -1) { + flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); + int32_t f, field_count = ecs_vec_count(&ef->ft_terms); + for (f = 0; f < field_count; f ++) { + flecs_flat_table_term_t *field = &fields[f]; + flecs_flat_monitor_t *tgt_mon = ecs_vec_first(&field->monitor); + int32_t tgt, tgt_count = ecs_vec_count(&field->monitor); + for (tgt = 0; tgt < tgt_count; tgt ++) { + tgt_mon[tgt].monitor = tgt_mon[tgt].table_state; + } + } + } + + query->prev_match_count = query->match_count; +} + +/* Check if single match term has changed */ +static +bool flecs_query_check_match_monitor_term( + ecs_query_t *query, + ecs_query_table_match_t *match, + int32_t term) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_query_get_match_monitor(query, match)) { + return true; + } + + int32_t *monitor = match->monitor; + int32_t state = monitor[term]; + if (state == -1) { + return false; + } + + ecs_table_t *table = match->table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + query->filter.world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (!term) { + return monitor[0] != dirty_state[0]; + } + } else if (!term) { + return false; + } + + flecs_table_column_t cur; + flecs_query_get_column_for_term(query, match, term - 1, &cur); + ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + + return monitor[term] != flecs_table_get_dirty_state( + query->filter.world, cur.table)[cur.column + 1]; +} + +/* Check if any term for match has changed */ +static +bool flecs_query_check_match_monitor( + ecs_query_t *query, + ecs_query_table_match_t *match, + const ecs_iter_t *it) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_query_get_match_monitor(query, match)) { + return true; + } + + int32_t *monitor = match->monitor; + ecs_table_t *table = match->table; + int32_t *dirty_state = NULL; + if (table) { + dirty_state = flecs_table_get_dirty_state( + query->filter.world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (monitor[0] != dirty_state[0]) { + return true; + } + } + + bool has_flat = false, is_this = false; + const ecs_filter_t *filter = &query->filter; + ecs_world_t *world = filter->world; + int32_t i, j, field_count = filter->field_count; + int32_t *storage_columns = match->storage_columns; + int32_t *columns = it ? it->columns : NULL; + if (!columns) { + columns = match->columns; + } + ecs_vec_t *refs = &match->refs; + for (i = 0; i < field_count; i ++) { + int32_t mon = monitor[i + 1]; + if (mon == -1) { + continue; + } + + int32_t column = storage_columns[i]; + if (columns[i] >= 0) { + /* owned component */ + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (mon != dirty_state[column + 1]) { + return true; + } + continue; + } else if (column == -1) { + continue; /* owned but not a component */ + } + + column = columns[i]; + if (!column) { + /* Not matched */ + continue; + } + + ecs_assert(column < 0, ECS_INTERNAL_ERROR, NULL); + column = -column; + + /* Find term index from field index, which differ when using || */ + int32_t term_index = i; + if (filter->terms[i].field_index != i) { + for (j = i; j < filter->term_count; j ++) { + if (filter->terms[j].field_index == i) { + term_index = j; + break; + } + } + } + + is_this = ecs_term_match_this(&filter->terms[term_index]); + + /* Flattened fields are encoded by adding field_count to the column + * index of the parent component. */ + if (is_this && it && (column > field_count)) { + has_flat = true; + } else { + if (is_this) { + /* Component reached through traversal from this */ + int32_t ref_index = column - 1; + ecs_ref_t *ref = ecs_vec_get_t(refs, ecs_ref_t, ref_index); + if (ref->id != 0) { + ecs_ref_update(world, ref); + ecs_table_record_t *tr = ref->tr; + ecs_table_t *src_table = tr->hdr.table; + column = tr->index; + column = ecs_table_type_to_column_index(src_table, column); + int32_t *src_dirty_state = flecs_table_get_dirty_state( + world, src_table); + if (mon != src_dirty_state[column + 1]) { + return true; + } + } + } else { + /* Component from static source */ + ecs_entity_t src = match->sources[i]; + ecs_table_t *src_table = ecs_get_table(world, src); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + column = ecs_table_type_to_column_index(src_table, column - 1); + int32_t *src_dirty_state = flecs_table_get_dirty_state( + world, src_table); + if (mon != src_dirty_state[column + 1]) { + return true; + } + } + } + } + + if (has_flat) { + ecs_entity_filter_t *ef = match->entity_filter; + flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); + ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; + int32_t cur_tgt = ent_it->target_count - 1; + field_count = ecs_vec_count(&ef->ft_terms); + + for (i = 0; i < field_count; i ++) { + flecs_flat_table_term_t *field = &fields[i]; + flecs_flat_monitor_t *fmon = ecs_vec_get_t(&field->monitor, + flecs_flat_monitor_t, cur_tgt); + if (fmon->monitor != fmon->table_state) { + return true; + } + } + } + + return false; +} + +/* Check if any term for matched table has changed */ +static +bool flecs_query_check_table_monitor( + ecs_query_t *query, + ecs_query_table_t *table, + int32_t term) +{ + ecs_query_table_match_t *cur, *end = table->last->next; + + for (cur = table->first; cur != end; cur = cur->next) { + ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; + if (term == -1) { + if (flecs_query_check_match_monitor(query, match, NULL)) { + return true; + } + } else { + if (flecs_query_check_match_monitor_term(query, match, term)) { + return true; + } + } + } + + return false; +} + +static +bool flecs_query_check_query_monitor( + ecs_query_t *query) +{ + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&query->cache, &it)) { + ecs_query_table_t *qt; + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + if (flecs_query_check_table_monitor(query, qt, -1)) { + return true; + } + } + } + + return false; +} + +static +void flecs_query_init_query_monitors( + ecs_query_t *query) +{ + ecs_query_table_match_t *cur = query->list.first; + + /* Ensure each match has a monitor */ + for (; cur != NULL; cur = cur->next) { + ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; + flecs_query_get_match_monitor(query, match); + } +} + +/* The group by function for cascade computes the tree depth for the table type. + * This causes tables in the query cache to be ordered by depth, which ensures + * breadth-first iteration order. */ +static +uint64_t flecs_query_group_by_cascade( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) +{ + (void)id; + ecs_term_t *term = ctx; + ecs_entity_t rel = term->src.trav; + int32_t depth = flecs_relation_depth(world, rel, table); + return flecs_ito(uint64_t, depth); +} + +static +void flecs_query_add_ref( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_table_match_t *qm, + ecs_entity_t component, + ecs_entity_t entity, + ecs_size_t size) +{ + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_ref_t *ref = ecs_vec_append_t(&world->allocator, &qm->refs, ecs_ref_t); + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + + if (size) { + *ref = ecs_ref_init_id(world, entity, component); + } else { + *ref = (ecs_ref_t){ + .entity = entity, + .id = 0 + }; + } + + query->flags |= EcsQueryHasRefs; +} + +static +ecs_query_table_match_t* flecs_query_add_table_match( + ecs_query_t *query, + ecs_query_table_t *qt, + ecs_table_t *table) +{ + /* Add match for table. One table can have more than one match, if + * the query contains wildcards. */ + ecs_query_table_match_t *qm = flecs_query_cache_add(query->filter.world, qt); + qm->table = table; + + qm->columns = flecs_balloc(&query->allocators.columns); + qm->storage_columns = flecs_balloc(&query->allocators.columns); + qm->ids = flecs_balloc(&query->allocators.ids); + qm->sources = flecs_balloc(&query->allocators.sources); + + /* Insert match to iteration list if table is not empty */ + if (!table || ecs_table_count(table) != 0) { + flecs_query_insert_table_node(query, qm); + } + + return qm; +} + +static +void flecs_query_set_table_match( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_table_match_t *qm, + ecs_table_t *table, + ecs_iter_t *it) +{ + ecs_allocator_t *a = &world->allocator; + ecs_filter_t *filter = &query->filter; + int32_t i, term_count = filter->term_count; + int32_t field_count = filter->field_count; + ecs_term_t *terms = filter->terms; + + /* Reset resources in case this is an existing record */ + ecs_vec_reset_t(a, &qm->refs, ecs_ref_t); + ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count); + ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); + ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); + + if (table) { + /* Initialize storage columns for faster access to component storage */ + for (i = 0; i < field_count; i ++) { + if (terms[i].inout == EcsInOutNone) { + qm->storage_columns[i] = -1; + continue; + } + + int32_t column = qm->columns[i]; + if (column > 0) { + qm->storage_columns[i] = ecs_table_type_to_column_index(table, + qm->columns[i] - 1); + } else { + /* Shared field (not from table) */ + qm->storage_columns[i] = -2; + } + } + + flecs_entity_filter_init(world, &qm->entity_filter, filter, + table, qm->ids, qm->columns); + + if (qm->entity_filter) { + query->flags &= ~EcsQueryTrivialIter; + } + if (table->flags & EcsTableHasUnion) { + query->flags &= ~EcsQueryTrivialIter; + } + } + + /* Add references for substituted terms */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (!ecs_term_match_this(term)) { + /* non-This terms are set during iteration */ + continue; + } + + int32_t field = terms[i].field_index; + ecs_entity_t src = it->sources[field]; + ecs_size_t size = 0; + if (it->sizes) { + size = it->sizes[field]; + } + if (src) { + ecs_id_t id = it->ids[field]; + ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); + + if (id) { + flecs_query_add_ref(world, query, qm, id, src, size); + + /* Use column index to bind term and ref */ + if (qm->columns[field] != 0) { + qm->columns[field] = -ecs_vec_count(&qm->refs); + } + } + } + } +} + +static +ecs_query_table_t* flecs_query_table_insert( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + ecs_query_table_t *qt = flecs_bcalloc(&world->allocators.query_table); + if (table) { + qt->table_id = table->id; + } else { + qt->table_id = 0; + } + ecs_table_cache_insert(&query->cache, table, &qt->hdr); + return qt; +} + +/** Populate query cache with tables */ +static +void flecs_query_match_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_table_t *table = NULL; + ecs_query_table_t *qt = NULL; + + ecs_iter_t it = ecs_filter_iter(world, &query->filter); + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + ECS_BIT_SET(it.flags, EcsIterNoData); + ECS_BIT_SET(it.flags, EcsIterTableOnly); + ECS_BIT_SET(it.flags, EcsIterEntityOptional); + + while (ecs_filter_next(&it)) { + if ((table != it.table) || (!it.table && !qt)) { + /* New table matched, add record to cache */ + table = it.table; + qt = flecs_query_table_insert(world, query, table); + } + + ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); + flecs_query_set_table_match(world, query, qm, table, &it); + } +} + +static +bool flecs_query_match_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + if (!ecs_map_is_init(&query->cache.index)) { + return false; + } + + ecs_query_table_t *qt = NULL; + ecs_filter_t *filter = &query->filter; + int var_id = ecs_filter_find_this_var(filter); + if (var_id == -1) { + /* If query doesn't match with This term, it can't match with tables */ + return false; + } + + ecs_iter_t it = flecs_filter_iter_w_flags(world, filter, EcsIterMatchVar| + EcsIterIsInstanced|EcsIterNoData|EcsIterEntityOptional); + ecs_iter_set_var_as_table(&it, var_id, table); + + while (ecs_filter_next(&it)) { + ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); + if (qt == NULL) { + table = it.table; + qt = flecs_query_table_insert(world, query, table); + } + + ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); + flecs_query_set_table_match(world, query, qm, table, &it); + } + + return qt != NULL; +} + +ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static) + +static +void flecs_query_sort_table( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare, + ecs_sort_table_action_t sort) +{ + ecs_data_t *data = &table->data; + if (!ecs_vec_count(&data->entities)) { + /* Nothing to sort */ + return; + } + + int32_t count = flecs_table_data_count(data); + if (count < 2) { + return; + } + + ecs_entity_t *entities = ecs_vec_first(&data->entities); + + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &data->columns[column_index]; + ecs_type_info_t *ti = column->ti; + size = ti->size; + ptr = ecs_vec_first(&column->data); + } + + if (sort) { + sort(world, table, entities, ptr, size, 0, count - 1, compare); + } else { + flecs_query_sort_table_generic(world, table, entities, ptr, size, 0, count - 1, compare); + } +} + +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_query_table_match_t *match; + 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 ECS_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 flecs_query_build_sorted_table_range( + ecs_query_t *query, + ecs_query_table_list_t *list) +{ + ecs_world_t *world = query->filter.world; + ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, + "cannot sort query in multithreaded mode"); + + ecs_entity_t id = query->order_by_component; + ecs_order_by_action_t compare = query->order_by; + int32_t table_count = list->info.table_count; + if (!table_count) { + return; + } + + ecs_vec_init_if_t(&query->table_slices, ecs_query_table_match_t); + int32_t to_sort = 0; + int32_t order_by_term = query->order_by_term; + + sort_helper_t *helper = flecs_alloc_n( + &world->allocator, sort_helper_t, table_count); + ecs_query_table_match_t *cur, *end = list->last->next; + for (cur = list->first; cur != end; cur = cur->next) { + ecs_table_t *table = cur->table; + ecs_data_t *data = &table->data; + + ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); + + if (id) { + const ecs_term_t *term = &query->filter.terms[order_by_term]; + int32_t field = term->field_index; + int32_t column = cur->columns[field]; + ecs_size_t size = query->filter.sizes[field]; + ecs_assert(column != 0, ECS_INTERNAL_ERROR, NULL); + if (column >= 0) { + column = table->column_map[column - 1]; + ecs_vec_t *vec = &data->columns[column].data; + helper[to_sort].ptr = ecs_vec_first(vec); + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; + } else { + ecs_entity_t src = cur->sources[field]; + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (term->src.flags & EcsUp) { + ecs_entity_t base = 0; + ecs_search_relation(world, r->table, 0, id, + EcsIsA, term->src.flags & EcsTraverseFlags, &base, 0, 0); + if (base && base != src) { /* Component could be inherited */ + r = flecs_entities_get(world, base); + } + } + + helper[to_sort].ptr = ecs_table_get_id( + world, r->table, id, ECS_RECORD_TO_ROW(r->row)); + helper[to_sort].elem_size = size; + helper[to_sort].shared = true; + } + ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; + } + + helper[to_sort].match = cur; + helper[to_sort].entities = ecs_vec_first(&data->entities); + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; + } + + ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, 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; + e1 = e_from_helper(&helper[min]); + } + } + + sort_helper_t *cur_helper = &helper[min]; + if (!cur || cur->columns != cur_helper->match->columns) { + cur = ecs_vec_append_t(NULL, &query->table_slices, + ecs_query_table_match_t); + *cur = *(cur_helper->match); + cur->offset = cur_helper->row; + cur->count = 1; + } else { + cur->count ++; + } + + cur_helper->row ++; + } while (proceed); + + /* Iterate through the vector of slices to set the prev/next ptrs. This + * can't be done while building the vector, as reallocs may occur */ + int32_t i, count = ecs_vec_count(&query->table_slices); + ecs_query_table_match_t *nodes = ecs_vec_first(&query->table_slices); + for (i = 0; i < count; i ++) { + nodes[i].prev = &nodes[i - 1]; + nodes[i].next = &nodes[i + 1]; + } + + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + + flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); +} + +static +void flecs_query_build_sorted_tables( + ecs_query_t *query) +{ + ecs_vec_clear(&query->table_slices); + + if (query->group_by) { + /* Populate sorted node list in grouping order */ + ecs_query_table_match_t *cur = query->list.first; + if (cur) { + do { + /* Find list for current group */ + uint64_t group_id = cur->group_id; + ecs_query_table_list_t *list = ecs_map_get_deref( + &query->groups, ecs_query_table_list_t, group_id); + ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Sort tables in current group */ + flecs_query_build_sorted_table_range(query, list); + + /* Find next group to sort */ + cur = list->last->next; + } while (cur); + } + } else { + flecs_query_build_sorted_table_range(query, &query->list); + } +} + +static +void flecs_query_sort_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_order_by_action_t compare = query->order_by; + if (!compare) { + return; + } + + ecs_sort_table_action_t sort = query->sort_table; + + ecs_entity_t order_by_component = query->order_by_component; + int32_t order_by_term = query->order_by_term; + + /* Iterate over non-empty tables. Don't bother with empty tables as they + * have nothing to sort */ + + bool tables_sorted = false; + + ecs_id_record_t *idr = flecs_id_record_get(world, order_by_component); + ecs_table_cache_iter_t it; + ecs_query_table_t *qt; + flecs_table_cache_iter(&query->cache, &it); + + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + ecs_table_t *table = qt->hdr.table; + bool dirty = false; + + if (flecs_query_check_table_monitor(query, qt, 0)) { + dirty = true; + } + + int32_t column = -1; + if (order_by_component) { + if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) { + dirty = true; + } + + if (dirty) { + column = -1; + + const ecs_table_record_t *tr = flecs_id_record_get_table( + idr, table); + if (tr) { + column = tr->column; + } + + if (column == -1) { + /* Component is shared, no sorting is needed */ + dirty = false; + } + } + } + + if (!dirty) { + continue; + } + + /* Something has changed, sort the table. Prefers using + * flecs_query_sort_table when available */ + flecs_query_sort_table(world, table, column, compare, sort); + tables_sorted = true; + } + + if (tables_sorted || query->match_count != query->prev_match_count) { + flecs_query_build_sorted_tables(query); + query->match_count ++; /* Increase version if tables changed */ + } +} + +static +bool flecs_query_has_refs( + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + for (i = 0; i < count; i ++) { + if (terms[i].src.flags & (EcsUp | EcsIsEntity)) { + return true; + } + } + + return false; +} + +static +void flecs_query_for_each_component_monitor( + ecs_world_t *world, + ecs_query_t *query, + void(*callback)( + ecs_world_t* world, + ecs_id_t id, + ecs_query_t *query)) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *src = &term->src; + + if (src->flags & EcsUp) { + callback(world, ecs_pair(src->trav, EcsWildcard), query); + if (src->trav != EcsIsA) { + callback(world, ecs_pair(EcsIsA, EcsWildcard), query); + } + callback(world, term->id, query); + + } else if (src->flags & EcsSelf && !ecs_term_match_this(term)) { + callback(world, term->id, query); + } + } +} + +static +bool flecs_query_is_term_id_supported( + ecs_term_id_t *term_id) +{ + if (!(term_id->flags & EcsIsVariable)) { + return true; + } + if (ecs_id_is_wildcard(term_id->id)) { + return true; + } + return false; +} + +static +int flecs_query_process_signature( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *first = &term->first; + ecs_term_id_t *src = &term->src; + ecs_term_id_t *second = &term->second; + ecs_inout_kind_t inout = term->inout; + + bool is_src_ok = flecs_query_is_term_id_supported(src); + bool is_first_ok = flecs_query_is_term_id_supported(first); + bool is_second_ok = flecs_query_is_term_id_supported(second); + + (void)first; + (void)second; + (void)is_src_ok; + (void)is_first_ok; + (void)is_second_ok; + + /* Queries do not support named variables */ + ecs_check(is_src_ok || ecs_term_match_this(term), + ECS_UNSUPPORTED, NULL); + ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); + ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); + ecs_check(!(src->flags & EcsFilter), ECS_INVALID_PARAMETER, + "invalid usage of Filter for query"); + + if (inout != EcsIn && inout != EcsInOutNone) { + /* Non-this terms default to EcsIn */ + if (ecs_term_match_this(term) || inout != EcsInOutDefault) { + query->flags |= EcsQueryHasOutTerms; + } + + bool match_non_this = !ecs_term_match_this(term) || + (term->src.flags & EcsUp); + if (match_non_this && inout != EcsInOutDefault) { + query->flags |= EcsQueryHasNonThisOutTerms; + } + } + + if (src->flags & EcsCascade) { + /* Query can only have one cascade column */ + ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); + query->cascade_by = i + 1; + } + } + + query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs); + + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_query_for_each_component_monitor(world, query, flecs_monitor_register); + } + + return 0; +error: + return -1; +} + +/** When a table becomes empty remove it from the query list, or vice versa. */ +static +void flecs_query_update_table( + ecs_query_t *query, + ecs_table_t *table, + bool empty) +{ + int32_t prev_count = ecs_query_table_count(query); + ecs_table_cache_set_empty(&query->cache, table, empty); + int32_t cur_count = ecs_query_table_count(query); + + if (prev_count != cur_count) { + ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); + ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_table_match_t *cur, *next; + + for (cur = qt->first; cur != NULL; cur = next) { + next = cur->next_match; + + if (empty) { + ecs_assert(ecs_table_count(table) == 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_query_remove_table_node(query, cur); + } else { + ecs_assert(ecs_table_count(table) != 0, + ECS_INTERNAL_ERROR, NULL); + flecs_query_insert_table_node(query, cur); + } + } + } + + ecs_assert(cur_count || query->list.first == NULL, + ECS_INTERNAL_ERROR, NULL); +} + +static +void flecs_query_add_subquery( + ecs_world_t *world, + ecs_query_t *parent, + ecs_query_t *subquery) +{ + ecs_vec_init_if_t(&parent->subqueries, ecs_query_t*); + ecs_query_t **elem = ecs_vec_append_t( + NULL, &parent->subqueries, ecs_query_t*); + *elem = subquery; + + ecs_table_cache_t *cache = &parent->cache; + ecs_table_cache_iter_t it; + ecs_query_table_t *qt; + flecs_table_cache_all_iter(cache, &it); + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + flecs_query_match_table(world, subquery, qt->hdr.table); + } +} + +static +void flecs_query_notify_subqueries( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + if (query->subqueries.array) { + ecs_query_t **queries = ecs_vec_first(&query->subqueries); + int32_t i, count = ecs_vec_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]; + flecs_query_notify(world, sub, &sub_event); + } + } +} + +/* Remove table */ +static +void flecs_query_table_match_free( + ecs_query_t *query, + ecs_query_table_t *elem, + ecs_query_table_match_t *first) +{ + ecs_query_table_match_t *cur, *next; + ecs_world_t *world = query->filter.world; + + for (cur = first; cur != NULL; cur = next) { + flecs_bfree(&query->allocators.columns, cur->columns); + flecs_bfree(&query->allocators.columns, cur->storage_columns); + flecs_bfree(&query->allocators.ids, cur->ids); + flecs_bfree(&query->allocators.sources, cur->sources); + + if (cur->monitor) { + flecs_bfree(&query->allocators.monitors, cur->monitor); + } + if (!elem->hdr.empty) { + flecs_query_remove_table_node(query, cur); + } + + ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); + flecs_entity_filter_fini(world, cur->entity_filter); + + next = cur->next_match; + + flecs_bfree(&world->allocators.query_table_match, cur); + } +} + +static +void flecs_query_table_free( + ecs_query_t *query, + ecs_query_table_t *elem) +{ + flecs_query_table_match_free(query, elem, elem->first); + flecs_bfree(&query->filter.world->allocators.query_table, elem); +} + +static +void flecs_query_unmatch_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_query_table_t *elem) +{ + if (!elem) { + elem = ecs_table_cache_get(&query->cache, table); + } + if (elem) { + ecs_table_cache_remove(&query->cache, elem->table_id, &elem->hdr); + flecs_query_table_free(query, elem); + } +} + +/* Rematch system with tables after a change happened to a watched entity */ +static +void flecs_query_rematch_tables( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_t *parent_query) +{ + ecs_iter_t it, parent_it; + ecs_table_t *table = NULL; + ecs_query_table_t *qt = NULL; + ecs_query_table_match_t *qm = NULL; + + if (query->monitor_generation == world->monitor_generation) { + return; + } + + query->monitor_generation = world->monitor_generation; + + if (parent_query) { + parent_it = ecs_query_iter(world, parent_query); + it = ecs_filter_chain_iter(&parent_it, &query->filter); + } else { + it = ecs_filter_iter(world, &query->filter); + } + + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + ECS_BIT_SET(it.flags, EcsIterNoData); + ECS_BIT_SET(it.flags, EcsIterEntityOptional); + + world->info.rematch_count_total ++; + int32_t rematch_count = ++ query->rematch_count; + + ecs_time_t t = {0}; + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_measure(&t); + } + + while (ecs_filter_next(&it)) { + if ((table != it.table) || (!it.table && !qt)) { + if (qm && qm->next_match) { + flecs_query_table_match_free(query, qt, qm->next_match); + qm->next_match = NULL; + } + + table = it.table; + + qt = ecs_table_cache_get(&query->cache, table); + if (!qt) { + qt = flecs_query_table_insert(world, query, table); + } + + ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + qt->rematch_count = rematch_count; + qm = NULL; + } + if (!qm) { + qm = qt->first; + } else { + qm = qm->next_match; + } + if (!qm) { + qm = flecs_query_add_table_match(query, qt, table); + } + + flecs_query_set_table_match(world, query, qm, table, &it); + + if (table && ecs_table_count(table) && query->group_by) { + if (flecs_query_get_group_id(query, table) != qm->group_id) { + /* Update table group */ + flecs_query_remove_table_node(query, qm); + flecs_query_insert_table_node(query, qm); + } + } + } + + if (qm && qm->next_match) { + flecs_query_table_match_free(query, qt, qm->next_match); + qm->next_match = NULL; + } + + /* Iterate all tables in cache, remove ones that weren't just matched */ + ecs_table_cache_iter_t cache_it; + if (flecs_table_cache_all_iter(&query->cache, &cache_it)) { + while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) { + if (qt->rematch_count != rematch_count) { + flecs_query_unmatch_table(query, qt->hdr.table, qt); + } + } + } + + if (world->flags & EcsWorldMeasureFrameTime) { + world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); + } +} + +static +void flecs_query_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.array, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vec_count(&parent->subqueries); + ecs_query_t **sq = ecs_vec_first(&parent->subqueries); + + for (i = 0; i < count; i ++) { + if (sq[i] == sub) { + break; + } + } + + ecs_vec_remove_t(&parent->subqueries, ecs_query_t*, i); +} + +/* -- Private API -- */ + +void flecs_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 (flecs_query_match_table(world, query, event->table)) { + if (query->subqueries.array) { + flecs_query_notify_subqueries(world, query, event); + } + } + notify = false; + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + flecs_query_unmatch_table(query, event->table, NULL); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + flecs_query_rematch_tables(world, query, event->parent_query); + break; + case EcsQueryOrphan: + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + query->flags |= EcsQueryIsOrphaned; + query->parent = NULL; + break; + } + + if (notify) { + flecs_query_notify_subqueries(world, query, event); + } +} + +static +void flecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t order_by_component, + ecs_order_by_action_t order_by, + ecs_sort_table_action_t action) +{ + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_id_is_wildcard(order_by_component), + ECS_INVALID_PARAMETER, NULL); + + /* Find order_by_component term & make sure it is queried for */ + const ecs_filter_t *filter = &query->filter; + int32_t i, count = filter->term_count; + int32_t order_by_term = -1; + + if (order_by_component) { + for (i = 0; i < count; i ++) { + ecs_term_t *term = &filter->terms[i]; + + /* Only And terms are supported */ + if (term->id == order_by_component && term->oper == EcsAnd) { + order_by_term = i; + break; + } + } + + ecs_check(order_by_term != -1, ECS_INVALID_PARAMETER, + "sorted component not is queried for"); + } + + query->order_by_component = order_by_component; + query->order_by = order_by; + query->order_by_term = order_by_term; + query->sort_table = action; + + ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); + flecs_query_sort_tables(world, query); + + if (!query->table_slices.array) { + flecs_query_build_sorted_tables(query); + } + + query->flags &= ~EcsQueryTrivialIter; +error: + return; +} + +static +void flecs_query_group_by( + ecs_query_t *query, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + /* Cannot change grouping once a query has been created */ + ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); + ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); + + if (!group_by) { + /* Builtin function that groups by relationship */ + group_by = flecs_query_default_group_by; + } + + query->group_by_id = sort_component; + query->group_by = group_by; + + ecs_map_init_w_params(&query->groups, + &query->filter.world->allocators.query_table_list); +error: + return; +} + +/* Implementation for iterable mixin */ +static +void flecs_query_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) +{ + ecs_poly_assert(poly, ecs_query_t); + + if (filter) { + iter[1] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly)); + iter[0] = ecs_term_chain_iter(&iter[1], filter); + } else { + iter[0] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly)); + } +} + +static +void flecs_query_on_event( + ecs_iter_t *it) +{ + /* Because this is the observer::run callback, checking if this is event is + * already handled is not done for us. */ + ecs_world_t *world = it->world; + ecs_observer_t *o = it->ctx; + if (o->last_event_id) { + if (o->last_event_id[0] == world->event_id) { + return; + } + o->last_event_id[0] = world->event_id; + } + + ecs_query_t *query = o->ctx; + ecs_table_t *table = it->table; + ecs_entity_t event = it->event; + + if (event == EcsOnTableCreate) { + /* Creation of new table */ + if (flecs_query_match_table(world, query, table)) { + if (query->subqueries.array) { + ecs_query_event_t evt = { + .kind = EcsQueryTableMatch, + .table = table, + .parent_query = query + }; + flecs_query_notify_subqueries(world, query, &evt); + } + } + return; + } + + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + /* The observer isn't doing the matching because the query can do it more + * efficiently by checking the table with the query cache. */ + if (ecs_table_cache_get(&query->cache, table) == NULL) { + return; + } + + if (event == EcsOnTableEmpty) { + flecs_query_update_table(query, table, true); + } else + if (event == EcsOnTableFill) { + flecs_query_update_table(query, table, false); + } else if (event == EcsOnTableDelete) { + /* Deletion of table */ + flecs_query_unmatch_table(query, table, NULL); + if (query->subqueries.array) { + ecs_query_event_t evt = { + .kind = EcsQueryTableUnmatch, + .table = table, + .parent_query = query + }; + flecs_query_notify_subqueries(world, query, &evt); + } + return; + } +} + +static +void flecs_query_table_cache_free( + ecs_query_t *query) +{ + ecs_table_cache_iter_t it; + ecs_query_table_t *qt; + + if (flecs_table_cache_all_iter(&query->cache, &it)) { + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + flecs_query_table_free(query, qt); + } + } + + ecs_table_cache_fini(&query->cache); +} + +static +void flecs_query_allocators_init( + ecs_query_t *query) +{ + int32_t field_count = query->filter.field_count; + if (field_count) { + flecs_ballocator_init(&query->allocators.columns, + field_count * ECS_SIZEOF(int32_t)); + flecs_ballocator_init(&query->allocators.ids, + field_count * ECS_SIZEOF(ecs_id_t)); + flecs_ballocator_init(&query->allocators.sources, + field_count * ECS_SIZEOF(ecs_entity_t)); + flecs_ballocator_init(&query->allocators.monitors, + (1 + field_count) * ECS_SIZEOF(int32_t)); + } +} + +static +void flecs_query_allocators_fini( + ecs_query_t *query) +{ + int32_t field_count = query->filter.field_count; + if (field_count) { + flecs_ballocator_fini(&query->allocators.columns); + flecs_ballocator_fini(&query->allocators.ids); + flecs_ballocator_fini(&query->allocators.sources); + flecs_ballocator_fini(&query->allocators.monitors); + } +} + +static +void flecs_query_fini( + ecs_query_t *query) +{ + ecs_world_t *world = query->filter.world; + + ecs_group_delete_action_t on_delete = query->on_group_delete; + if (on_delete) { + ecs_map_iter_t it = ecs_map_iter(&query->groups); + while (ecs_map_next(&it)) { + ecs_query_table_list_t *group = ecs_map_ptr(&it); + uint64_t group_id = ecs_map_key(&it); + on_delete(world, group_id, group->info.ctx, query->group_by_ctx); + } + query->on_group_delete = NULL; + } + + if (query->group_by_ctx_free) { + if (query->group_by_ctx) { + query->group_by_ctx_free(query->group_by_ctx); + } + } + + if ((query->flags & EcsQueryIsSubquery) && + !(query->flags & EcsQueryIsOrphaned)) + { + flecs_query_remove_subquery(query->parent, query); + } + + flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryOrphan + }); + + flecs_query_for_each_component_monitor(world, query, + flecs_monitor_unregister); + flecs_query_table_cache_free(query); + + ecs_map_fini(&query->groups); + + ecs_vec_fini_t(NULL, &query->subqueries, ecs_query_t*); + ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); + ecs_filter_fini(&query->filter); + + flecs_query_allocators_fini(query); + + if (query->ctx_free) { + query->ctx_free(query->ctx); + } + if (query->binding_ctx_free) { + query->binding_ctx_free(query->binding_ctx); + } + + ecs_poly_free(query, ecs_query_t); +} + +/* -- Public API -- */ + +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *desc) +{ + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); + + ecs_query_t *result = ecs_poly_new(ecs_query_t); + ecs_observer_desc_t observer_desc = { .filter = desc->filter }; + ecs_entity_t entity = desc->filter.entity; + + observer_desc.filter.flags = EcsFilterMatchEmptyTables; + observer_desc.filter.storage = &result->filter; + result->filter = ECS_FILTER_INIT; + + if (ecs_filter_init(world, &observer_desc.filter) == NULL) { + goto error; + } + + ECS_BIT_COND(result->flags, EcsQueryTrivialIter, + !!(result->filter.flags & EcsFilterMatchOnlyThis)); + + flecs_query_allocators_init(result); + + if (result->filter.term_count) { + observer_desc.entity = entity; + observer_desc.run = flecs_query_on_event; + observer_desc.ctx = result; + observer_desc.events[0] = EcsOnTableEmpty; + observer_desc.events[1] = EcsOnTableFill; + if (!desc->parent) { + observer_desc.events[2] = EcsOnTableCreate; + observer_desc.events[3] = EcsOnTableDelete; + } + observer_desc.filter.flags |= EcsFilterNoData; + observer_desc.filter.instanced = true; + + /* ecs_filter_init could have moved away resources from the terms array + * in the descriptor, so use the terms array from the filter. */ + observer_desc.filter.terms_buffer = result->filter.terms; + observer_desc.filter.terms_buffer_count = result->filter.term_count; + observer_desc.filter.expr = NULL; /* Already parsed */ + + entity = ecs_observer_init(world, &observer_desc); + if (!entity) { + goto error; + } + } + + result->iterable.init = flecs_query_iter_init; + result->dtor = (ecs_poly_dtor_t)flecs_query_fini; + result->prev_match_count = -1; + + result->ctx = desc->ctx; + result->binding_ctx = desc->binding_ctx; + result->ctx_free = desc->ctx_free; + result->binding_ctx_free = desc->binding_ctx_free; + + if (ecs_should_log_1()) { + char *filter_expr = ecs_filter_str(world, &result->filter); + ecs_dbg_1("#[green]query#[normal] [%s] created", + filter_expr ? filter_expr : ""); + ecs_os_free(filter_expr); + } + + ecs_log_push_1(); + + if (flecs_query_process_signature(world, result)) { + goto error; + } + + /* Group before matching so we won't have to move tables around later */ + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + flecs_query_group_by(result, result->filter.terms[cascade_by - 1].id, + flecs_query_group_by_cascade); + result->group_by_ctx = &result->filter.terms[cascade_by - 1]; + } + + if (desc->group_by || desc->group_by_id) { + /* Can't have a cascade term and group by at the same time, as cascade + * uses the group_by mechanism */ + ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); + flecs_query_group_by(result, desc->group_by_id, desc->group_by); + result->group_by_ctx = desc->group_by_ctx; + result->on_group_create = desc->on_group_create; + result->on_group_delete = desc->on_group_delete; + result->group_by_ctx_free = desc->group_by_ctx_free; + } + + if (desc->parent != NULL) { + result->flags |= EcsQueryIsSubquery; + } + + /* If the query refers to itself, add the components that were queried for + * to the query itself. */ + if (entity) { + int32_t t, term_count = result->filter.term_count; + ecs_term_t *terms = result->filter.terms; + + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (term->src.id == entity) { + ecs_add_id(world, entity, term->id); + } + } + } + + if (!entity) { + entity = ecs_new_id(world); + } + + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_query_t); + if (poly->poly) { + /* If entity already had poly query, delete previous */ + flecs_query_fini(poly->poly); + } + poly->poly = result; + result->filter.entity = entity; + + /* Ensure that while initially populating the query with tables, they are + * in the right empty/non-empty list. This ensures the query won't miss + * empty/non-empty events for tables that are currently out of sync, but + * change back to being in sync before processing pending events. */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + ecs_table_cache_init(world, &result->cache); + + if (!desc->parent) { + flecs_query_match_tables(world, result); + } else { + flecs_query_add_subquery(world, desc->parent, result); + result->parent = desc->parent; + } + + if (desc->order_by) { + flecs_query_order_by( + world, result, desc->order_by_component, desc->order_by, + desc->sort_table); + } + + if (!ecs_query_table_count(result) && result->filter.term_count) { + ecs_add_id(world, entity, EcsEmpty); + } + + ecs_poly_modified(world, entity, ecs_query_t); + + ecs_log_pop_1(); + + return result; +error: + if (result) { + ecs_filter_fini(&result->filter); + ecs_os_free(result); + } + return NULL; +} + +void ecs_query_fini( + ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + ecs_delete(query->filter.world, query->filter.entity); +} + +const ecs_filter_t* ecs_query_get_filter( + const ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + return &query->filter; +} + +static +void flecs_query_set_var( + ecs_iter_t *it) +{ + ecs_check(it->constrained_vars == 1, ECS_INVALID_OPERATION, + "can only set $this variable for queries"); + + ecs_var_t *var = &it->variables[0]; + ecs_table_t *table = var->range.table; + if (!table) { + goto nodata; + } + + ecs_query_iter_t *qit = &it->priv.iter.query; + ecs_query_t *query = qit->query; + ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); + if (!qt) { + goto nodata; + } + + qit->node = qt->first; + qit->last = qt->last->next_match; + it->offset = var->range.offset; + it->count = var->range.count; + return; +error: +nodata: + it->priv.iter.query.node = NULL; + it->priv.iter.query.last = NULL; + return; +} + +ecs_iter_t ecs_query_iter( + const ecs_world_t *stage, + ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); + + ecs_world_t *world = query->filter.world; + ecs_poly_assert(world, ecs_world_t); + + /* Process table events to ensure that the list of iterated tables doesn't + * contain empty tables. */ + flecs_process_pending_tables(world); + + /* If query has order_by, apply sort */ + flecs_query_sort_tables(world, query); + + /* If monitors changed, do query rematching */ + if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(world); + } + + /* Prepare iterator */ + + int32_t table_count; + if (ecs_vec_count(&query->table_slices)) { + table_count = ecs_vec_count(&query->table_slices); + } else { + table_count = ecs_query_table_count(query); + } + + ecs_query_iter_t it = { + .query = query, + .node = query->list.first, + .last = NULL + }; + + if (query->order_by && query->list.info.table_count) { + it.node = ecs_vec_first(&query->table_slices); + } + + ecs_iter_t result = { + .real_world = world, + .world = ECS_CONST_CAST(ecs_world_t*, stage), + .query = &query->filter, + .terms = query->filter.terms, + .field_count = query->filter.field_count, + .table_count = table_count, + .variable_count = 1, + .priv.iter.query = it, + .next = ecs_query_next, + .set_var = flecs_query_set_var, + .system = query->filter.entity + }; + + flecs_filter_apply_iter_flags(&result, &query->filter); + + ecs_filter_t *filter = &query->filter; + ecs_iter_t fit; + if (!(query->flags & EcsQueryTrivialIter)) { + /* Check if non-This terms (like singleton terms) still match */ + if (!(filter->flags & EcsFilterMatchOnlyThis)) { + fit = flecs_filter_iter_w_flags(ECS_CONST_CAST(ecs_world_t*, stage), + &query->filter, EcsIterIgnoreThis); + if (!ecs_filter_next(&fit)) { + /* No match, so return nothing */ + goto noresults; + } + } + + flecs_iter_init(stage, &result, flecs_iter_cache_all); + + /* Copy the data */ + if (!(filter->flags & EcsFilterMatchOnlyThis)) { + int32_t field_count = filter->field_count; + if (field_count) { + if (result.ptrs) { + ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, field_count); + } + ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, field_count); + ecs_os_memcpy_n(result.columns, fit.columns, int32_t, field_count); + ecs_os_memcpy_n(result.sources, fit.sources, int32_t, field_count); + } + ecs_iter_fini(&fit); + } + } else { + /* Trivial iteration, use arrays from query cache */ + flecs_iter_init(stage, &result, + flecs_iter_cache_ptrs|flecs_iter_cache_variables); + } + + result.sizes = query->filter.sizes; + + return result; +error: +noresults: + result.priv.iter.query.node = NULL; + return result; +} + +void ecs_query_set_group( + ecs_iter_t *it, + uint64_t group_id) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); + + ecs_query_iter_t *qit = &it->priv.iter.query; + ecs_query_t *q = qit->query; + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_query_table_list_t *node = flecs_query_get_group(q, group_id); + if (!node) { + qit->node = NULL; + return; + } + + ecs_query_table_match_t *first = node->first; + if (first) { + qit->node = node->first; + qit->last = node->last->next; + } else { + qit->node = NULL; + qit->last = NULL; + } + +error: + return; +} + +const ecs_query_group_info_t* ecs_query_get_group_info( + const ecs_query_t *query, + uint64_t group_id) +{ + ecs_query_table_list_t *node = flecs_query_get_group(query, group_id); + if (!node) { + return NULL; + } + + return &node->info; +} + +void* ecs_query_get_group_ctx( + const ecs_query_t *query, + uint64_t group_id) +{ + const ecs_query_group_info_t *info = + ecs_query_get_group_info(query, group_id); + if (!info) { + return NULL; + } else { + return info->ctx; + } +} + +static +void flecs_query_mark_columns_dirty( + ecs_query_t *query, + ecs_query_table_match_t *qm) +{ + ecs_table_t *table = qm->table; + ecs_filter_t *filter = &query->filter; + if ((table && table->dirty_state) || (query->flags & EcsQueryHasNonThisOutTerms)) { + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_inout_kind_t inout = term->inout; + if (inout == EcsIn || inout == EcsInOutNone) { + /* Don't mark readonly terms dirty */ + continue; + } + + flecs_table_column_t tc; + flecs_query_get_column_for_term(query, qm, i, &tc); + + if (tc.column == -1) { + continue; + } + + ecs_assert(tc.table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *dirty_state = tc.table->dirty_state; + if (!dirty_state) { + continue; + } + + if (table != tc.table) { + if (inout == EcsInOutDefault) { + continue; + } + } + + ecs_assert(tc.column >= 0, ECS_INTERNAL_ERROR, NULL); + + dirty_state[tc.column + 1] ++; + } + } +} + +bool ecs_query_next_table( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + + flecs_iter_validate(it); + + ecs_query_iter_t *iter = &it->priv.iter.query; + ecs_query_table_match_t *node = iter->node; + ecs_query_t *query = iter->query; + + ecs_query_table_match_t *prev = iter->prev; + if (prev) { + if (query->flags & EcsQueryHasMonitor) { + flecs_query_sync_match_monitor(query, prev); + } + if (query->flags & EcsQueryHasOutTerms) { + if (it->count) { + flecs_query_mark_columns_dirty(query, prev); + } + } + } + + if (node != iter->last) { + it->table = node->table; + it->group_id = node->group_id; + it->count = 0; + iter->node = node->next; + iter->prev = node; + return true; + } + +error: + query->match_count = query->prev_match_count; + ecs_iter_fini(it); + return false; +} + +static +void flecs_query_populate_trivial( + ecs_iter_t *it, + ecs_query_table_match_t *match) +{; + ecs_table_t *table = match->table; + int32_t offset, count; + if (!it->constrained_vars) { + it->offset = offset = 0; + it->count = count = ecs_table_count(table); + } else { + offset = it->offset; + count = it->count; + } + + it->ids = match->ids; + it->sources = match->sources; + it->columns = match->columns; + it->group_id = match->group_id; + it->instance_count = 0; + it->references = ecs_vec_first(&match->refs); + + if (!it->references) { + ecs_data_t *data = &table->data; + if (!(it->flags & EcsIterNoData)) { + int32_t i; + for (i = 0; i < it->field_count; i ++) { + int32_t column = match->storage_columns[i]; + if (column < 0) { + it->ptrs[i] = NULL; + continue; + } + + ecs_size_t size = it->sizes[i]; + if (!size) { + it->ptrs[i] = NULL; + continue; + } + + it->ptrs[i] = ecs_vec_get(&data->columns[column].data, + it->sizes[i], offset); + } + } + + it->frame_offset += it->table ? ecs_table_count(it->table) : 0; + it->table = table; + it->entities = ecs_vec_get_t(&data->entities, ecs_entity_t, offset); + } else { + flecs_iter_populate_data( + it->real_world, it, table, offset, count, it->ptrs); + } +} + +int ecs_query_populate( + ecs_iter_t *it, + bool when_changed) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); + + ecs_query_iter_t *iter = &it->priv.iter.query; + ecs_query_t *query = iter->query; + ecs_query_table_match_t *match = iter->prev; + ecs_assert(match != NULL, ECS_INVALID_OPERATION, NULL); + if (query->flags & EcsQueryTrivialIter) { + flecs_query_populate_trivial(it, match); + return EcsIterNextYield; + } + + ecs_table_t *table = match->table; + ecs_world_t *world = query->filter.world; + const ecs_filter_t *filter = &query->filter; + ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; + ecs_assert(ent_it != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_range_t *range = &ent_it->range; + int32_t t, term_count = filter->term_count; + int result; + +repeat: + result = EcsIterNextYield; + + ecs_os_memcpy_n(it->sources, match->sources, ecs_entity_t, + filter->field_count); + + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &filter->terms[t]; + int32_t field = term->field_index; + if (!ecs_term_match_this(term)) { + continue; + } + + it->ids[field] = match->ids[field]; + it->columns[field] = match->columns[field]; + } + + if (table) { + range->offset = match->offset; + range->count = match->count; + if (!range->count) { + range->count = ecs_table_count(table); + ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL); + } + + if (match->entity_filter) { + ent_it->entity_filter = match->entity_filter; + ent_it->columns = match->columns; + ent_it->range.table = table; + ent_it->it = it; + result = flecs_entity_filter_next(ent_it); + if (result == EcsIterNext) { + goto done; + } + } + + it->group_id = match->group_id; + } else { + range->offset = 0; + range->count = 0; + } + + if (when_changed) { + if (!ecs_query_changed(NULL, it)) { + if (result == EcsIterYield) { + goto repeat; + } else { + result = EcsIterNext; + goto done; + } + } + } + + it->references = ecs_vec_first(&match->refs); + it->instance_count = 0; + + flecs_iter_populate_data(world, it, table, range->offset, range->count, + it->ptrs); + +error: +done: + return result; +} + +bool ecs_query_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); +error: + return false; +} + +bool ecs_query_next_instanced( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + + ecs_query_iter_t *iter = &it->priv.iter.query; + ecs_query_t *query = iter->query; + ecs_flags32_t flags = query->flags; + + ecs_query_table_match_t *prev, *next, *cur = iter->node, *last = iter->last; + if ((prev = iter->prev)) { + /* Match has been iterated, update monitor for change tracking */ + if (flags & EcsQueryHasMonitor) { + flecs_query_sync_match_monitor(query, prev); + } + if (flags & EcsQueryHasOutTerms) { + flecs_query_mark_columns_dirty(query, prev); + } + } + + flecs_iter_validate(it); + iter->skip_count = 0; + + /* Trivial iteration: each entry in the cache is a full match and ids are + * only matched on $this or through traversal starting from $this. */ + if (flags & EcsQueryTrivialIter) { + if (cur == last) { + goto done; + } + iter->node = cur->next; + iter->prev = cur; + flecs_query_populate_trivial(it, cur); + return true; + } + + /* Non-trivial iteration: query matches with static sources, or matches with + * tables that require per-entity filtering. */ + for (; cur != last; cur = next) { + next = cur->next; + iter->prev = cur; + switch(ecs_query_populate(it, false)) { + case EcsIterNext: iter->node = next; continue; + case EcsIterYield: next = cur; /* fall through */ + case EcsIterNextYield: goto yield; + default: ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + } + +done: error: + query->match_count = query->prev_match_count; + ecs_iter_fini(it); + return false; + +yield: + iter->node = next; + iter->prev = cur; + return true; +} + +bool ecs_query_changed( + ecs_query_t *query, + const ecs_iter_t *it) +{ + if (it) { + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); + + ecs_query_table_match_t *qm = + (ecs_query_table_match_t*)it->priv.iter.query.prev; + ecs_assert(qm != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!query) { + query = it->priv.iter.query.query; + } else { + ecs_check(query == it->priv.iter.query.query, + ECS_INVALID_PARAMETER, NULL); + } + + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(query, ecs_query_t); + + return flecs_query_check_match_monitor(query, qm, it); + } + + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); + + flecs_process_pending_tables(query->filter.world); + + if (!(query->flags & EcsQueryHasMonitor)) { + query->flags |= EcsQueryHasMonitor; + flecs_query_init_query_monitors(query); + return true; /* Monitors didn't exist yet */ + } + + if (query->match_count != query->prev_match_count) { + return true; + } + + return flecs_query_check_query_monitor(query); +error: + return false; +} + +void ecs_query_skip( + ecs_iter_t *it) +{ + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); + + if (it->instance_count > it->count) { + it->priv.iter.query.skip_count ++; + if (it->priv.iter.query.skip_count == it->instance_count) { + /* For non-instanced queries, make sure all entities are skipped */ + it->priv.iter.query.prev = NULL; + } + } else { + it->priv.iter.query.prev = NULL; + } +} + +bool ecs_query_orphaned( + const ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + return query->flags & EcsQueryIsOrphaned; +} + +char* ecs_query_str( + const ecs_query_t *query) +{ + return ecs_filter_str(query->filter.world, &query->filter); +} + +int32_t ecs_query_table_count( + const ecs_query_t *query) +{ + ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); + return query->cache.tables.count; +} + +int32_t ecs_query_empty_table_count( + const ecs_query_t *query) +{ + ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); + return query->cache.empty_tables.count; +} + +int32_t ecs_query_entity_count( + const ecs_query_t *query) +{ + ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); + + int32_t result = 0; + ecs_table_cache_hdr_t *cur, *last = query->cache.tables.last; + if (!last) { + return 0; + } + + for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) { + result += ecs_table_count(cur->table); + } + + return result; +} + +void* ecs_query_get_ctx( + const ecs_query_t *query) +{ + return query->ctx; +} + +void* ecs_query_get_binding_ctx( + const ecs_query_t *query) +{ + return query->binding_ctx; +} + +/** + * @file search.c + * @brief Search functions to find (component) ids in table types. + * + * Search functions are used to find the column index of a (component) id in a + * table. Additionally, search functions implement the logic for finding a + * component id by following a relationship upwards. + */ + + +static +int32_t flecs_type_search( + const ecs_table_t *table, + ecs_id_t search_id, + ecs_id_record_t *idr, + ecs_id_t *ids, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); + if (tr) { + int32_t r = tr->index; + if (tr_out) tr_out[0] = tr; + if (id_out) { + if (ECS_PAIR_FIRST(search_id) == EcsUnion) { + id_out[0] = ids[r]; + } else { + id_out[0] = flecs_to_public_id(ids[r]); + } + } + return r; + } + + return -1; +} + +static +int32_t flecs_type_offset_search( + int32_t offset, + ecs_id_t id, + ecs_id_t *ids, + int32_t count, + ecs_id_t *id_out) +{ + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + while (offset < count) { + ecs_id_t type_id = ids[offset ++]; + if (ecs_id_match(type_id, id)) { + if (id_out) { + id_out[0] = flecs_to_public_id(type_id); + } + return offset - 1; + } + } + + return -1; +} + +bool flecs_type_can_inherit_id( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_id_record_t *idr, + ecs_id_t id) +{ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + if (idr->flags & EcsIdDontInherit) { + return false; + } + if (idr->flags & EcsIdExclusive) { + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t er = ECS_PAIR_FIRST(id); + if (flecs_table_record_get( + world, table, ecs_pair(er, EcsWildcard))) + { + return false; + } + } + } + return true; +} + +static +int32_t flecs_type_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_record_t *idr, + ecs_id_t rel, + ecs_id_record_t *idr_r, + bool self, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t count = type.count; + + if (self) { + if (offset) { + int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out); + if (r != -1) { + return r; + } + } else { + int32_t r = flecs_type_search(table, id, idr, ids, id_out, tr_out); + if (r != -1) { + return r; + } + } + } + + ecs_flags32_t flags = table->flags; + if ((flags & EcsTableHasPairs) && rel) { + bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); + if (is_a) { + if (!(flags & EcsTableHasIsA)) { + return -1; + } + idr_r = world->idr_isa_wildcard; + + if (!flecs_type_can_inherit_id(world, table, idr, id)) { + return -1; + } + } + + if (!idr_r) { + idr_r = flecs_id_record_get(world, rel); + if (!idr_r) { + return -1; + } + } + + ecs_id_t id_r; + int32_t r, r_column; + if (offset) { + r_column = flecs_type_offset_search(offset, rel, ids, count, &id_r); + } else { + r_column = flecs_type_search(table, id, idr_r, ids, &id_r, 0); + } + while (r_column != -1) { + ecs_entity_t obj = ECS_PAIR_SECOND(id_r); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *rec = flecs_entities_get_any(world, obj); + ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *obj_table = rec->table; + if (obj_table) { + ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); + + r = flecs_type_search_relation(world, obj_table, 0, id, idr, + rel, idr_r, true, subject_out, id_out, tr_out); + if (r != -1) { + if (subject_out && !subject_out[0]) { + subject_out[0] = ecs_get_alive(world, obj); + } + return r_column; + } + + if (!is_a) { + r = flecs_type_search_relation(world, obj_table, 0, id, idr, + ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, + true, subject_out, id_out, tr_out); + if (r != -1) { + if (subject_out && !subject_out[0]) { + subject_out[0] = ecs_get_alive(world, obj); + } + return r_column; + } + } + } + + r_column = flecs_type_offset_search( + r_column + 1, rel, ids, count, &id_r); + } + } + + return -1; +} + +int32_t flecs_search_relation_w_idr( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out, + ecs_id_record_t *idr) +{ + if (!table) return -1; + + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + flags = flags ? flags : (EcsSelf|EcsUp); + + if (!idr) { + idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; + } + } + + if (subject_out) subject_out[0] = 0; + if (!(flags & EcsUp)) { + if (offset) { + return ecs_search_offset(world, table, offset, id, id_out); + } else { + return flecs_type_search( + table, id, idr, table->type.array, id_out, tr_out); + } + } + + int32_t result = flecs_type_search_relation(world, table, offset, id, idr, + ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, + id_out, tr_out); + + return result; +} + +int32_t ecs_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out) +{ + if (!table) return -1; + + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + flags = flags ? flags : (EcsSelf|EcsUp); + + if (subject_out) subject_out[0] = 0; + if (!(flags & EcsUp)) { + return ecs_search_offset(world, table, offset, id, id_out); + } + + ecs_id_record_t *idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; + } + + int32_t result = flecs_type_search_relation(world, table, offset, id, idr, + ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, + id_out, tr_out); + + return result; +} + +int32_t flecs_search_w_idr( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out, + ecs_id_record_t *idr) +{ + if (!table) return -1; + + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + (void)world; + + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + return flecs_type_search(table, id, idr, ids, id_out, 0); +} + +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out) +{ + if (!table) return -1; + + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_id_record_t *idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; + } + + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + return flecs_type_search(table, id, idr, ids, id_out, 0); +} + +int32_t ecs_search_offset( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_t *id_out) +{ + if (!offset) { + ecs_poly_assert(world, ecs_world_t); + return ecs_search(world, table, id, id_out); + } + + if (!table) return -1; + + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t count = type.count; + return flecs_type_offset_search(offset, id, ids, count, id_out); +} + +static +int32_t flecs_relation_depth_walk( + const ecs_world_t *world, + const ecs_id_record_t *idr, + const ecs_table_t *first, + const ecs_table_t *table) +{ + int32_t result = 0; + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return 0; + } + + int32_t i = tr->index, end = i + tr->count; + for (; i != end; i ++) { + ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); + ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *ot = ecs_get_table(world, o); + if (!ot) { + continue; + } + + ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); + int32_t cur = flecs_relation_depth_walk(world, idr, first, ot); + if (cur > result) { + result = cur; + } + } + + return result + 1; +} + +int32_t flecs_relation_depth( + const ecs_world_t *world, + ecs_entity_t r, + const ecs_table_t *table) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); + if (!idr) { + return 0; + } + + int32_t depth_offset = 0; + if (table->flags & EcsTableHasTarget) { + if (ecs_table_get_type_index(world, table, + ecs_pair_t(EcsTarget, r)) != -1) + { + ecs_id_t id; + int32_t col = ecs_search(world, table, + ecs_pair(EcsFlatten, EcsWildcard), &id); + if (col == -1) { + return 0; + } + + ecs_entity_t did = ecs_pair_second(world, id); + ecs_assert(did != 0, ECS_INTERNAL_ERROR, NULL); + uint64_t *val = ecs_map_get(&world->store.entity_to_depth, did); + ecs_assert(val != NULL, ECS_INTERNAL_ERROR, NULL); + depth_offset = flecs_uto(int32_t, val[0]); + } + } + + return flecs_relation_depth_walk(world, idr, table, table) + depth_offset; +} + +/** + * @file stage.c + * @brief Staging implementation. + * + * A stage is an object that can be used to temporarily store mutations to a + * world while a world is in readonly mode. ECS operations that are invoked on + * a stage are stored in a command buffer, which is flushed during sync points, + * or manually by the user. + * + * Stages contain additional state to enable other API functionality without + * having to mutate the world, such as setting the current scope, and allocators + * that are local to a stage. + * + * In a multi threaded application, each thread has its own stage which allows + * threads to insert mutations without having to lock administration. + */ + + +static +ecs_cmd_t* flecs_cmd_new( + ecs_stage_t *stage) +{ + ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, + ecs_cmd_t); + cmd->is._1.value = NULL; + cmd->next_for_entity = 0; + cmd->entry = NULL; + return cmd; +} + +static +ecs_cmd_t* flecs_cmd_new_batched( + ecs_stage_t *stage, + ecs_entity_t e) +{ + ecs_vec_t *cmds = &stage->cmd->queue; + ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( + &stage->cmd->entries, ecs_cmd_entry_t, e); + + int32_t cur = ecs_vec_count(cmds); + ecs_cmd_t *cmd = flecs_cmd_new(stage); + if (entry) { + if (entry->first == -1) { + /* Existing but invalidated entry */ + entry->first = cur; + cmd->entry = entry; + } else { + int32_t last = entry->last; + ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); + ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *last_op = &arr[last]; + last_op->next_for_entity = cur; + if (last == entry->first) { + /* Flip sign bit so flush logic can tell which command + * is the first for an entity */ + last_op->next_for_entity *= -1; + } + } + } else { + cmd->entry = entry = flecs_sparse_ensure_fast_t( + &stage->cmd->entries, ecs_cmd_entry_t, e); + entry->first = cur; + } + + entry->last = cur; + + return cmd; +} + +static +void flecs_stages_merge( + ecs_world_t *world, + bool force_merge) +{ + bool is_stage = ecs_poly_is(world, ecs_stage_t); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + bool measure_frame_time = ECS_BIT_IS_SET(world->flags, + EcsWorldMeasureFrameTime); + + ecs_time_t t_start = {0}; + if (measure_frame_time) { + ecs_os_get_time(&t_start); + } + + ecs_dbg_3("#[magenta]merge"); + ecs_log_push_3(); + + if (is_stage) { + /* Check for consistency if force_merge is enabled. In practice this + * function will never get called with force_merge disabled for just + * a single stage. */ + if (force_merge || stage->auto_merge) { + ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, + "mismatching defer_begin/defer_end detected"); + flecs_defer_end(world, stage); + } + } else { + /* Merge stages. Only merge if the stage has auto_merging turned on, or + * if this is a forced merge (like when ecs_merge is called) */ + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_poly_assert(s, ecs_stage_t); + if (force_merge || s->auto_merge) { + flecs_defer_end(world, s); + } + } + } + + flecs_eval_component_monitors(world); + + if (measure_frame_time) { + world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); + } + + world->info.merge_count_total ++; + + /* If stage is asynchronous, deferring is always enabled */ + if (stage->async) { + flecs_defer_begin(world, stage); + } + + ecs_log_pop_3(); +} + +static +void flecs_stage_auto_merge( + ecs_world_t *world) +{ + flecs_stages_merge(world, false); +} + +static +void flecs_stage_manual_merge( + ecs_world_t *world) +{ + flecs_stages_merge(world, true); +} + +bool flecs_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); + (void)world; + if (stage->defer < 0) return false; + return (++ stage->defer) == 1; +} + +bool flecs_defer_cmd( + ecs_stage_t *stage) +{ + if (stage->defer) { + return (stage->defer > 0); + } + + stage->defer ++; + return false; +} + +bool flecs_defer_modified( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + if (cmd) { + cmd->kind = EcsCmdModified; + cmd->id = id; + cmd->entity = entity; + } + return true; + } + return false; +} + +bool flecs_defer_clone( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdClone; + cmd->id = src; + cmd->entity = entity; + cmd->is._1.clone_value = clone_value; + return true; + } + return false; +} + +bool flecs_defer_path( + ecs_stage_t *stage, + ecs_entity_t parent, + ecs_entity_t entity, + const char *name) +{ + if (stage->defer > 0) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdPath; + cmd->entity = entity; + cmd->id = parent; + cmd->is._1.value = ecs_os_strdup(name); + return true; + } + return false; +} + +bool flecs_defer_delete( + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdDelete; + cmd->entity = entity; + return true; + } + return false; +} + +bool flecs_defer_clear( + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdClear; + cmd->entity = entity; + return true; + } + return false; +} + +bool flecs_defer_on_delete_action( + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdOnDeleteAction; + cmd->id = id; + cmd->entity = action; + return true; + } + return false; +} + +bool flecs_defer_enable( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; + cmd->entity = entity; + cmd->id = id; + return true; + } + return false; +} + +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_id_t id, + const ecs_entity_t **ids_out) +{ + if (flecs_defer_cmd(stage)) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); + + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new_id(world); + } + + *ids_out = ids; + + /* Store data in op */ + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdBulkNew; + cmd->id = id; + cmd->is._n.entities = ids; + cmd->is._n.count = count; + cmd->entity = 0; + return true; + } + return false; +} + +bool flecs_defer_add( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + if (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdAdd; + cmd->id = id; + cmd->entity = entity; + return true; + } + return false; +} + +bool flecs_defer_remove( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + if (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdRemove; + cmd->id = id; + cmd->entity = entity; + return true; + } + return false; +} + +void* flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_cmd_kind_t cmd_kind, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + void *value) +{ + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + + /* Find type info for id */ + const ecs_type_info_t *ti = NULL; + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + /* If idr doesn't exist yet, create it but only if the + * application is not multithreaded. */ + if (stage->async || (world->flags & EcsWorldMultiThreaded)) { + ti = ecs_get_type_info(world, id); + ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); + } else { + /* When not in multi threaded mode, it's safe to find or + * create the id record. */ + idr = flecs_id_record_ensure(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get type_info from id record. We could have called + * ecs_get_type_info directly, but since this function can be + * expensive for pairs, creating the id record ensures we can + * find the type_info quickly for subsequent operations. */ + ti = idr->type_info; + } + } else { + ti = idr->type_info; + } + + /* If the id isn't associated with a type, we can't set anything */ + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Make sure the size of the value equals the type size */ + ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); + size = ti->size; + + /* Find existing component. Make sure it's owned, so that we won't use the + * component of a prefab. */ + void *existing = NULL; + ecs_table_t *table = NULL; + if (idr) { + /* Entity can only have existing component if id record exists */ + ecs_record_t *r = flecs_entities_get(world, entity); + table = r->table; + if (r && table) { + const ecs_table_record_t *tr = flecs_id_record_get_table( + idr, table); + if (tr) { + ecs_assert(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); + /* Entity has the component */ + ecs_vec_t *column = &table->data.columns[tr->column].data; + existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); + } + } + } + + /* Get existing value from storage */ + void *cmd_value = existing; + bool emplace = cmd_kind == EcsCmdEmplace; + + /* If the component does not yet exist, create a temporary value. This is + * necessary so we can store a component value in the deferred command, + * without adding the component to the entity which is not allowed in + * deferred mode. */ + if (!existing) { + ecs_stack_t *stack = &stage->cmd->stack; + cmd_value = flecs_stack_alloc(stack, size, ti->alignment); + + /* If the component doesn't yet exist, construct it and move the + * provided value into the component, if provided. Don't construct if + * this is an emplace operation, in which case the application is + * responsible for constructing. */ + if (value) { + if (emplace) { + ecs_move_t move = ti->hooks.move_ctor; + if (move) { + move(cmd_value, value, 1, ti); + } else { + ecs_os_memcpy(cmd_value, value, size); + } + } else { + ecs_copy_t copy = ti->hooks.copy_ctor; + if (copy) { + copy(cmd_value, value, 1, ti); + } else { + ecs_os_memcpy(cmd_value, value, size); + } + } + } else if (!emplace) { + /* If the command is not an emplace, construct the temp storage */ + + /* Check if entity inherits component */ + void *base = NULL; + if (table && (table->flags & EcsTableHasIsA)) { + base = flecs_get_base_component(world, table, id, idr, 0); + } + + if (!base) { + /* Normal ctor */ + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(cmd_value, 1, ti); + } + } else { + /* Override */ + ecs_copy_t copy = ti->hooks.copy_ctor; + if (copy) { + copy(cmd_value, base, 1, ti); + } else { + ecs_os_memcpy(cmd_value, base, size); + } + } + } + } else if (value) { + /* If component exists and value is provided, copy */ + ecs_copy_t copy = ti->hooks.copy; + if (copy) { + copy(existing, value, 1, ti); + } else { + ecs_os_memcpy(existing, value, size); + } + } + + if (!cmd) { + /* If cmd is NULL, entity was already deleted. Check if we need to + * insert a command into the queue. */ + if (!ti->hooks.dtor) { + /* If temporary memory does not need to be destructed, it'll get + * freed when the stack allocator is reset. This prevents us + * from having to insert a command when the entity was + * already deleted. */ + return cmd_value; + } + cmd = flecs_cmd_new(stage); + } + + if (!existing) { + /* If component didn't exist yet, insert command that will create it */ + cmd->kind = cmd_kind; + cmd->id = id; + cmd->idr = idr; + cmd->entity = entity; + cmd->is._1.size = size; + cmd->is._1.value = cmd_value; + } else { + /* If component already exists, still insert an Add command to ensure + * that any preceding remove commands won't remove the component. If the + * operation is a set, also insert a Modified command. */ + if (cmd_kind == EcsCmdSet) { + cmd->kind = EcsCmdAddModified; + } else { + cmd->kind = EcsCmdAdd; + } + cmd->id = id; + cmd->entity = entity; + } + + return cmd_value; +error: + return NULL; +} + +void flecs_enqueue( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_event_desc_t *desc) +{ + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdEvent; + cmd->entity = desc->entity; + + ecs_stack_t *stack = &stage->cmd->stack; + ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); + ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); + + if (desc->ids && desc->ids->count != 0) { + ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); + int32_t id_count = desc->ids->count; + type_cmd->count = id_count; + type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); + ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); + desc_cmd->ids = type_cmd; + } else { + desc_cmd->ids = NULL; + } + + cmd->is._1.value = desc_cmd; + cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); + + if (desc->param || desc->const_param) { + ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, + "cannot set param and const_param at the same time"); + + const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); + ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, + "can only enqueue events with data for events that are components"); + + void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); + ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); + if (desc->param) { + if (ti->hooks.move_ctor) { + ti->hooks.move_ctor(param_cmd, desc->param, 1, ti); + } else { + ecs_os_memcpy(param_cmd, desc->param, ti->size); + } + } else { + if (ti->hooks.copy_ctor) { + ti->hooks.copy_ctor(param_cmd, desc->const_param, 1, ti); + } else { + ecs_os_memcpy(param_cmd, desc->const_param, ti->size); + } + } + + desc_cmd->param = param_cmd; + desc_cmd->const_param = NULL; + } +} + +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) +{ + /* Execute post frame actions */ + int32_t i, count = ecs_vec_count(&stage->post_frame_actions); + ecs_action_elem_t *elems = ecs_vec_first(&stage->post_frame_actions); + for (i = 0; i < count; i ++) { + elems[i].action(world, elems[i].ctx); + } + + ecs_vec_clear(&stage->post_frame_actions); +} + +static +void flecs_commands_init( + ecs_stage_t *stage, + ecs_commands_t *cmd) +{ + flecs_stack_init(&cmd->stack); + ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); + flecs_sparse_init_t(&cmd->entries, &stage->allocator, + &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); +} + +static +void flecs_commands_fini( + ecs_stage_t *stage, + ecs_commands_t *cmd) +{ + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vec_count(&stage->cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); + + flecs_stack_fini(&cmd->stack); + ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); + flecs_sparse_fini(&cmd->entries); +} + +void flecs_commands_push( + ecs_stage_t *stage) +{ + int32_t sp = ++ stage->cmd_sp; + ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); + stage->cmd = &stage->cmd_stack[sp]; +} + +void flecs_commands_pop( + ecs_stage_t *stage) +{ + int32_t sp = -- stage->cmd_sp; + ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); + stage->cmd = &stage->cmd_stack[sp]; +} + +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_poly_init(stage, ecs_stage_t); + + stage->world = world; + stage->thread_ctx = world; + stage->auto_merge = true; + stage->async = false; + + flecs_stack_init(&stage->allocators.iter_stack); + flecs_stack_init(&stage->allocators.deser_stack); + flecs_allocator_init(&stage->allocator); + flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, + FLECS_SPARSE_PAGE_SIZE); + + ecs_allocator_t *a = &stage->allocator; + ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); + + int32_t i; + for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { + flecs_commands_init(stage, &stage->cmd_stack[i]); + } + + stage->cmd = &stage->cmd_stack[0]; +} + +void flecs_stage_fini( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); + + ecs_poly_fini(stage, ecs_stage_t); + + ecs_allocator_t *a = &stage->allocator; + + ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); + ecs_vec_fini(NULL, &stage->variables, 0); + ecs_vec_fini(NULL, &stage->operations, 0); + + int32_t i; + for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { + flecs_commands_fini(stage, &stage->cmd_stack[i]); + } + + flecs_stack_fini(&stage->allocators.iter_stack); + flecs_stack_fini(&stage->allocators.deser_stack); + flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); + flecs_allocator_fini(&stage->allocator); +} + +void ecs_set_stage_count( + ecs_world_t *world, + int32_t stage_count) +{ + ecs_poly_assert(world, ecs_world_t); + + /* World must have at least one default stage */ + ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), + ECS_INTERNAL_ERROR, NULL); + + bool auto_merge = true; + const ecs_entity_t *lookup_path = NULL; + ecs_entity_t scope = 0; + ecs_entity_t with = 0; + if (world->stage_count >= 1) { + auto_merge = world->stages[0].auto_merge; + lookup_path = world->stages[0].lookup_path; + scope = world->stages[0].scope; + with = world->stages[0].with; + } + + int32_t i, count = world->stage_count; + if (count && count != stage_count) { + ecs_stage_t *stages = world->stages; + + for (i = 0; i < count; i ++) { + /* If stage contains a thread handle, ecs_set_threads was used to + * create the stages. ecs_set_threads and ecs_set_stage_count should not + * be mixed. */ + ecs_poly_assert(&stages[i], ecs_stage_t); + ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); + flecs_stage_fini(world, &stages[i]); + } + + ecs_os_free(world->stages); + } + + if (stage_count) { + world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count); + + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = &world->stages[i]; + flecs_stage_init(world, stage); + stage->id = i; + + /* Set thread_ctx to stage, as this stage might be used in a + * multithreaded context */ + stage->thread_ctx = (ecs_world_t*)stage; + stage->thread = 0; + } + } else { + /* Set to NULL to prevent double frees */ + world->stages = NULL; + } + + /* Regardless of whether the stage was just initialized or not, when the + * ecs_set_stage_count function is called, all stages inherit the auto_merge + * property from the world */ + for (i = 0; i < stage_count; i ++) { + world->stages[i].auto_merge = auto_merge; + world->stages[i].lookup_path = lookup_path; + world->stages[0].scope = scope; + world->stages[0].with = with; + } + + world->stage_count = stage_count; +error: + return; +} + +int32_t ecs_get_stage_count( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return world->stage_count; +} + +int32_t ecs_get_stage_id( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ecs_poly_is(world, ecs_stage_t)) { + ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); + + /* Index 0 is reserved for main stage */ + return stage->id; + } else if (ecs_poly_is(world, ecs_world_t)) { + return 0; + } else { + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return 0; +} + +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); + return (ecs_world_t*)&world->stages[stage_id]; +error: + return NULL; +} + +bool ecs_readonly_begin( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + + flecs_process_pending_tables(world); + + ecs_dbg_3("#[bold]readonly"); + ecs_log_push_3(); + + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *stage = &world->stages[i]; + stage->lookup_path = world->stages[0].lookup_path; + ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, + "deferred mode cannot be enabled when entering readonly mode"); + flecs_defer_begin(world, stage); + } + + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); + + /* From this point on, the world is "locked" for mutations, and it is only + * allowed to enqueue commands from stages */ + ECS_BIT_SET(world->flags, EcsWorldReadonly); + + /* If world has more than one stage, signal we might be running on multiple + * threads. This is a stricter version of readonly mode: while some + * mutations like implicit component registration are still allowed in plain + * readonly mode, no mutations are allowed when multithreaded. */ + if (world->worker_cond) { + ECS_BIT_SET(world->flags, EcsWorldMultiThreaded); + } + + return is_readonly; +} + +void ecs_readonly_end( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); + + /* After this it is safe again to mutate the world directly */ + ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); + ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); + + ecs_log_pop_3(); + + flecs_stage_auto_merge(world); +error: + return; +} + +void ecs_merge( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); + flecs_stage_manual_merge(world); +error: + return; +} + +void ecs_set_automerge( + ecs_world_t *world, + bool auto_merge) +{ + /* If a world is provided, set auto_merge globally for the world. This + * doesn't actually do anything (the main stage never merges) but it serves + * as the default for when stages are created. */ + if (ecs_poly_is(world, ecs_world_t)) { + world->stages[0].auto_merge = auto_merge; + + /* Propagate change to all stages */ + int i, stage_count = ecs_get_stage_count(world); + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + stage->auto_merge = auto_merge; + } + + /* If a stage is provided, override the auto_merge value for the individual + * stage. This allows an application to control per-stage which stage should + * be automatically merged and which one shouldn't */ + } else { + ecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + stage->auto_merge = auto_merge; + } +} + +bool ecs_stage_is_readonly( + const ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); + + if (ecs_poly_is(stage, ecs_stage_t)) { + if (((const ecs_stage_t*)stage)->async) { + return false; + } + } + + if (world->flags & EcsWorldReadonly) { + if (ecs_poly_is(stage, ecs_world_t)) { + return true; + } + } else { + if (ecs_poly_is(stage, ecs_stage_t)) { + return true; + } + } + + return false; +} + +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world) +{ + ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); + flecs_stage_init(world, stage); + + stage->id = -1; + stage->auto_merge = false; + stage->async = true; + + flecs_defer_begin(world, stage); + + return (ecs_world_t*)stage; +} + +void ecs_async_stage_free( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + ecs_check(stage->async == true, ECS_INVALID_PARAMETER, NULL); + flecs_stage_fini(stage->world, stage); + ecs_os_free(stage); +error: + return; +} + +bool ecs_stage_is_async( + ecs_world_t *stage) +{ + if (!stage) { + return false; + } + + if (!ecs_poly_is(stage, ecs_stage_t)) { + return false; + } + + return ((ecs_stage_t*)stage)->async; +} + +bool ecs_is_deferred( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->defer > 0; +error: + return false; +} + +/** + * @file value.c + * @brief Utility functions to work with non-trivial pointers of user types. + */ + + +int ecs_value_init_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + ecs_xtor_t ctor; + if ((ctor = ti->hooks.ctor)) { + ctor(ptr, 1, ti); + } else { + ecs_os_memset(ptr, 0, ti->size); + } + + return 0; +error: + return -1; +} + +int ecs_value_init( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) +{ + ecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_init_w_type_info(world, ti, ptr); +error: + return -1; +} + +void* ecs_value_new_w_type_info( + ecs_world_t *world, + const ecs_type_info_t *ti) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + void *result = flecs_alloc(&world->allocator, ti->size); + if (ecs_value_init_w_type_info(world, ti, result) != 0) { + flecs_free(&world->allocator, ti->size, result); + goto error; + } + + return result; +error: + return NULL; +} + +void* ecs_value_new( + ecs_world_t *world, + ecs_entity_t type) +{ + ecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + + return ecs_value_new_w_type_info(world, ti); +error: + return NULL; +} + +int ecs_value_fini_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + ecs_xtor_t dtor; + if ((dtor = ti->hooks.dtor)) { + dtor(ptr, 1, ti); + } + + return 0; +error: + return -1; +} + +int ecs_value_fini( + const ecs_world_t *world, + ecs_entity_t type, + void* ptr) +{ + ecs_poly_assert(world, ecs_world_t); + (void)world; + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_fini_w_type_info(world, ti, ptr); +error: + return -1; +} + +int ecs_value_free( + ecs_world_t *world, + ecs_entity_t type, + void* ptr) +{ + ecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) { + goto error; + } + + flecs_free(&world->allocator, ti->size, ptr); + + return 0; +error: + return -1; +} + +int ecs_value_copy_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + const void *src) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + ecs_copy_t copy; + if ((copy = ti->hooks.copy)) { + copy(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); + } + + return 0; +error: + return -1; +} + +int ecs_value_copy( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + const void *src) +{ + ecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_copy_w_type_info(world, ti, dst, src); +error: + return -1; +} + +int ecs_value_move_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + ecs_move_t move; + if ((move = ti->hooks.move)) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); + } + + return 0; +error: + return -1; +} + +int ecs_value_move( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src) +{ + ecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_move_w_type_info(world, ti, dst, src); +error: + return -1; +} + +int ecs_value_move_ctor_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + ecs_move_t move; + if ((move = ti->hooks.move_ctor)) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); + } + + return 0; +error: + return -1; +} + +int ecs_value_move_ctor( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src) +{ + ecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_move_w_type_info(world, ti, dst, src); +error: + return -1; +} + +/** + * @file world.c + * @brief World-level API. + */ + + +/* Id flags */ +const ecs_id_t ECS_PAIR = (1ull << 63); +const ecs_id_t ECS_OVERRIDE = (1ull << 62); +const ecs_id_t ECS_TOGGLE = (1ull << 61); +const ecs_id_t ECS_AND = (1ull << 60); + +/** Builtin component ids */ +const ecs_entity_t ecs_id(EcsComponent) = 1; +const ecs_entity_t ecs_id(EcsIdentifier) = 2; +const ecs_entity_t ecs_id(EcsIterable) = 3; +const ecs_entity_t ecs_id(EcsPoly) = 4; + +/* Poly target components */ +const ecs_entity_t EcsQuery = 5; +const ecs_entity_t EcsObserver = 6; +const ecs_entity_t EcsSystem = 7; + +/* Core scopes & entities */ +const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 0; +const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 1; +const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 2; +const ecs_entity_t EcsFlecsInternals = FLECS_HI_COMPONENT_ID + 3; +const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 4; +const ecs_entity_t EcsPrivate = FLECS_HI_COMPONENT_ID + 5; +const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 6; +const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 7; + +const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 8; +const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 9; + +/* Relationship properties */ +const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 10; +const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 11; +const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 12; +const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 13; +const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 14; +const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 15; +const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 16; +const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 17; +const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 18; +const ecs_entity_t EcsAlwaysOverride = FLECS_HI_COMPONENT_ID + 19; +const ecs_entity_t EcsTag = FLECS_HI_COMPONENT_ID + 20; +const ecs_entity_t EcsUnion = FLECS_HI_COMPONENT_ID + 21; +const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 22; +const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 23; +const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 24; +const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 25; +const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 26; + +/* Builtin relationships */ +const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 27; +const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 28; +const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 29; + +/* Identifier tags */ +const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 30; +const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 31; +const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 32; + +/* Events */ +const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 33; +const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 34; +const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 35; +const ecs_entity_t EcsUnSet = FLECS_HI_COMPONENT_ID + 36; +const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 37; +const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 38; +const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 39; +const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 40; +const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 41; +const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 46; + +/* Timers */ +const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 47; +const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 48; +const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 49; + +/* Actions */ +const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 50; +const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 51; +const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 52; + +/* Misc */ +const ecs_entity_t ecs_id(EcsTarget) = FLECS_HI_COMPONENT_ID + 53; +const ecs_entity_t EcsFlatten = FLECS_HI_COMPONENT_ID + 54; +const ecs_entity_t EcsDefaultChildComponent = FLECS_HI_COMPONENT_ID + 55; + +/* Builtin predicate ids (used by rule engine) */ +const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 56; +const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 57; +const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 58; +const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 59; +const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 60; + +/* Systems */ +const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 61; +const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 62; +const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 63; +const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 64; +const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 65; +const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 66; +const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 67; +const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 68; +const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 69; +const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 70; +const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 71; +const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 72; +const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 73; +const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 74; +const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 75; + +/* Meta primitive components (don't use low ids to save id space) */ +const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 80; +const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 81; +const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 82; +const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 83; +const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 84; +const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 85; +const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 86; +const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 87; +const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 88; +const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 89; +const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 90; +const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 91; +const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 92; +const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 93; +const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 94; +const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 95; +const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 96; +const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 97; + +/** Meta module component ids */ +const ecs_entity_t ecs_id(EcsMetaType) = FLECS_HI_COMPONENT_ID + 98; +const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = FLECS_HI_COMPONENT_ID + 99; +const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 100; +const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 101; +const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 102; +const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 103; +const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 104; +const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 105; +const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 106; +const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; +const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; +const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 109; +const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 110; +const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; +const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 112; + +/* Doc module components */ +const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 113; +const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 114; +const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 115; +const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 116; +const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 117; + +/* REST module components */ +const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 118; + +/* Default lookup path */ +static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; + +/* Declarations for addons. Located in world.c to avoid issues during linking of + * static library */ +#ifdef FLECS_ALERTS +ECS_COMPONENT_DECLARE(EcsAlert); +ECS_COMPONENT_DECLARE(EcsAlertInstance); +ECS_COMPONENT_DECLARE(EcsAlertsActive); +ECS_TAG_DECLARE(EcsAlertInfo); +ECS_TAG_DECLARE(EcsAlertWarning); +ECS_TAG_DECLARE(EcsAlertError); +ECS_TAG_DECLARE(EcsAlertCritical); +#endif +#ifdef FLECS_UNITS +ECS_DECLARE(EcsUnitPrefixes); + +ECS_DECLARE(EcsYocto); +ECS_DECLARE(EcsZepto); +ECS_DECLARE(EcsAtto); +ECS_DECLARE(EcsFemto); +ECS_DECLARE(EcsPico); +ECS_DECLARE(EcsNano); +ECS_DECLARE(EcsMicro); +ECS_DECLARE(EcsMilli); +ECS_DECLARE(EcsCenti); +ECS_DECLARE(EcsDeci); +ECS_DECLARE(EcsDeca); +ECS_DECLARE(EcsHecto); +ECS_DECLARE(EcsKilo); +ECS_DECLARE(EcsMega); +ECS_DECLARE(EcsGiga); +ECS_DECLARE(EcsTera); +ECS_DECLARE(EcsPeta); +ECS_DECLARE(EcsExa); +ECS_DECLARE(EcsZetta); +ECS_DECLARE(EcsYotta); + +ECS_DECLARE(EcsKibi); +ECS_DECLARE(EcsMebi); +ECS_DECLARE(EcsGibi); +ECS_DECLARE(EcsTebi); +ECS_DECLARE(EcsPebi); +ECS_DECLARE(EcsExbi); +ECS_DECLARE(EcsZebi); +ECS_DECLARE(EcsYobi); + +ECS_DECLARE(EcsDuration); + ECS_DECLARE(EcsPicoSeconds); + ECS_DECLARE(EcsNanoSeconds); + ECS_DECLARE(EcsMicroSeconds); + ECS_DECLARE(EcsMilliSeconds); + ECS_DECLARE(EcsSeconds); + ECS_DECLARE(EcsMinutes); + ECS_DECLARE(EcsHours); + ECS_DECLARE(EcsDays); + +ECS_DECLARE(EcsTime); + ECS_DECLARE(EcsDate); + +ECS_DECLARE(EcsMass); + ECS_DECLARE(EcsGrams); + ECS_DECLARE(EcsKiloGrams); + +ECS_DECLARE(EcsElectricCurrent); + ECS_DECLARE(EcsAmpere); + +ECS_DECLARE(EcsAmount); + ECS_DECLARE(EcsMole); + +ECS_DECLARE(EcsLuminousIntensity); + ECS_DECLARE(EcsCandela); + +ECS_DECLARE(EcsForce); + ECS_DECLARE(EcsNewton); + +ECS_DECLARE(EcsLength); + ECS_DECLARE(EcsMeters); + ECS_DECLARE(EcsPicoMeters); + ECS_DECLARE(EcsNanoMeters); + ECS_DECLARE(EcsMicroMeters); + ECS_DECLARE(EcsMilliMeters); + ECS_DECLARE(EcsCentiMeters); + ECS_DECLARE(EcsKiloMeters); + ECS_DECLARE(EcsMiles); + ECS_DECLARE(EcsPixels); + +ECS_DECLARE(EcsPressure); + ECS_DECLARE(EcsPascal); + ECS_DECLARE(EcsBar); + +ECS_DECLARE(EcsSpeed); + ECS_DECLARE(EcsMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerHour); + ECS_DECLARE(EcsMilesPerHour); + +ECS_DECLARE(EcsAcceleration); + +ECS_DECLARE(EcsTemperature); + ECS_DECLARE(EcsKelvin); + ECS_DECLARE(EcsCelsius); + ECS_DECLARE(EcsFahrenheit); + +ECS_DECLARE(EcsData); + ECS_DECLARE(EcsBits); + ECS_DECLARE(EcsKiloBits); + ECS_DECLARE(EcsMegaBits); + ECS_DECLARE(EcsGigaBits); + ECS_DECLARE(EcsBytes); + ECS_DECLARE(EcsKiloBytes); + ECS_DECLARE(EcsMegaBytes); + ECS_DECLARE(EcsGigaBytes); + ECS_DECLARE(EcsKibiBytes); + ECS_DECLARE(EcsGibiBytes); + ECS_DECLARE(EcsMebiBytes); + +ECS_DECLARE(EcsDataRate); + ECS_DECLARE(EcsBitsPerSecond); + ECS_DECLARE(EcsKiloBitsPerSecond); + ECS_DECLARE(EcsMegaBitsPerSecond); + ECS_DECLARE(EcsGigaBitsPerSecond); + ECS_DECLARE(EcsBytesPerSecond); + ECS_DECLARE(EcsKiloBytesPerSecond); + ECS_DECLARE(EcsMegaBytesPerSecond); + ECS_DECLARE(EcsGigaBytesPerSecond); + +ECS_DECLARE(EcsPercentage); + +ECS_DECLARE(EcsAngle); + ECS_DECLARE(EcsRadians); + ECS_DECLARE(EcsDegrees); + +ECS_DECLARE(EcsBel); +ECS_DECLARE(EcsDeciBel); + +ECS_DECLARE(EcsFrequency); + ECS_DECLARE(EcsHertz); + ECS_DECLARE(EcsKiloHertz); + ECS_DECLARE(EcsMegaHertz); + ECS_DECLARE(EcsGigaHertz); + +ECS_DECLARE(EcsUri); + ECS_DECLARE(EcsUriHyperlink); + ECS_DECLARE(EcsUriImage); + ECS_DECLARE(EcsUriFile); +#endif + +/* -- Private functions -- */ + +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world) +{ + ecs_assert(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); + + if (ecs_poly_is(world, ecs_world_t)) { + return &world->stages[0]; + + } else if (ecs_poly_is(world, ecs_stage_t)) { + return ECS_CONST_CAST(ecs_stage_t*, world); + } + + return NULL; +} + +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr) +{ + ecs_world_t *world = *world_ptr; + + ecs_assert(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); + + if (ecs_poly_is(world, ecs_world_t)) { + return &world->stages[0]; + } + + *world_ptr = ((ecs_stage_t*)world)->world; + return ECS_CONST_CAST(ecs_stage_t*, world); +} + +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *stage_world, + ecs_suspend_readonly_state_t *state) +{ + ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_world_t *world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); + ecs_poly_assert(world, ecs_world_t); + + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); + ecs_world_t *temp_world = world; + ecs_stage_t *stage = flecs_stage_from_world(&temp_world); + + if (!is_readonly && !stage->defer) { + state->is_readonly = false; + state->is_deferred = false; + return world; + } + + ecs_dbg_3("suspending readonly mode"); + + /* Cannot suspend when running with multiple threads */ + ecs_assert(!(world->flags & EcsWorldReadonly) || + (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); + + state->is_readonly = is_readonly; + state->is_deferred = stage->defer != 0; + + /* Silence readonly checks */ + world->flags &= ~EcsWorldReadonly; + + /* Hack around safety checks (this ought to look ugly) */ + state->defer_count = stage->defer; + state->commands = stage->cmd->queue; + state->defer_stack = stage->cmd->stack; + flecs_stack_init(&stage->cmd->stack); + state->scope = stage->scope; + state->with = stage->with; + stage->defer = 0; + ecs_vec_init_t(NULL, &stage->cmd->queue, ecs_cmd_t, 0); + + return world; +} + +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_world_t *temp_world = world; + ecs_stage_t *stage = flecs_stage_from_world(&temp_world); + + if (state->is_readonly || state->is_deferred) { + ecs_dbg_3("resuming readonly mode"); + + ecs_run_aperiodic(world, 0); + + /* Restore readonly state / defer count */ + ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); + stage->defer = state->defer_count; + ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); + stage->cmd->queue = state->commands; + flecs_stack_fini(&stage->cmd->stack); + stage->cmd->stack = state->defer_stack; + stage->scope = state->scope; + stage->with = state->with; + } +} + +/* 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 flecs_eval_component_monitor( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + + if (!world->monitors.is_dirty) { + return; + } + + world->monitors.is_dirty = false; + + ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); + while (ecs_map_next(&it)) { + ecs_monitor_t *m = ecs_map_ptr(&it); + if (!m->is_dirty) { + continue; + } + + m->is_dirty = false; + + int32_t i, count = ecs_vec_count(&m->queries); + ecs_query_t **elems = ecs_vec_first(&m->queries); + for (i = 0; i < count; i ++) { + ecs_query_t *q = elems[i]; + flecs_query_notify(world, q, &(ecs_query_event_t) { + .kind = EcsQueryTableRematch + }); + } + } +} + +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t id) +{ + ecs_map_t *monitors = &world->monitors.monitors; + + /* Only flag if there are actually monitors registered, so that we + * don't waste cycles evaluating monitors if there's no interest */ + if (ecs_map_is_init(monitors)) { + ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); + if (m) { + if (!world->monitors.is_dirty) { + world->monitor_generation ++; + } + m->is_dirty = true; + world->monitors.is_dirty = true; + } + } +} + +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *monitors = &world->monitors.monitors; + ecs_map_init_if(monitors, &world->allocator); + ecs_monitor_t *m = ecs_map_ensure_alloc_t(monitors, ecs_monitor_t, id); + ecs_vec_init_if_t(&m->queries, ecs_query_t*); + ecs_query_t **q = ecs_vec_append_t( + &world->allocator, &m->queries, ecs_query_t*); + *q = query; +} + +void flecs_monitor_unregister( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *monitors = &world->monitors.monitors; + if (!ecs_map_is_init(monitors)) { + return; + } + + ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); + if (!m) { + return; + } + + int32_t i, count = ecs_vec_count(&m->queries); + ecs_query_t **queries = ecs_vec_first(&m->queries); + for (i = 0; i < count; i ++) { + if (queries[i] == query) { + ecs_vec_remove_t(&m->queries, ecs_query_t*, i); + count --; + break; + } + } + + if (!count) { + ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); + ecs_map_remove_free(monitors, id); + } + + if (!ecs_map_count(monitors)) { + ecs_map_fini(monitors); + } +} + +static +void flecs_init_store( + ecs_world_t *world) +{ + ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); + + ecs_allocator_t *a = &world->allocator; + ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); + ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); + ecs_vec_init_t(a, &world->store.depth_ids, ecs_entity_t, 0); + ecs_map_init(&world->store.entity_to_depth, &world->allocator); + + /* Initialize entity index */ + flecs_entities_init(world); + + /* Initialize root table */ + flecs_sparse_init_t(&world->store.tables, + a, &world->allocators.sparse_chunk, ecs_table_t); + + /* Initialize table map */ + flecs_table_hashmap_init(world, &world->store.table_map); + + /* Initialize one root table per stage */ + flecs_init_root_table(world); +} + +static +void flecs_clean_tables( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(&world->store.tables); + + /* Ensure that first table in sparse set has id 0. This is a dummy table + * that only exists so that there is no table with id 0 */ + ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, + ecs_table_t, 0); + (void)first; + + for (i = 1; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, + ecs_table_t, i); + flecs_table_free(world, t); + } + + /* Free table types separately so that if application destructors rely on + * a type it's still valid. */ + for (i = 1; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, + ecs_table_t, i); + flecs_table_free_type(world, t); + } + + /* Clear the root table */ + if (count) { + flecs_table_reset(world, &world->store.root); + } +} + +static +void flecs_fini_root_tables( + ecs_world_t *world, + ecs_id_record_t *idr, + bool fini_targets) +{ + ecs_table_cache_iter_t it; + + bool has_roots = flecs_table_cache_iter(&idr->cache, &it); + ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); + (void)has_roots; + + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (table->flags & EcsTableHasBuiltins) { + continue; /* Filter out modules */ + } + + int32_t i, count = table->data.entities.count; + ecs_entity_t *entities = table->data.entities.array; + + if (fini_targets) { + /* Only delete entities that are used as pair target. Iterate + * backwards to minimize moving entities around in table. */ + for (i = count - 1; i >= 0; i --) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { + ecs_delete(world, entities[i]); + } + } + } else { + /* Delete remaining entities that are not in use (added to another + * entity). This limits table moves during cleanup and delays + * cleanup of tags. */ + for (i = count - 1; i >= 0; i --) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { + ecs_delete(world, entities[i]); + } + } + } + } +} + +static +void flecs_fini_roots( + ecs_world_t *world) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); + + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* Delete root entities that are not modules. This prioritizes deleting + * regular entities first, which reduces the chance of components getting + * destructed in random order because it got deleted before entities, + * thereby bypassing the OnDeleteTarget policy. */ + flecs_defer_begin(world, &world->stages[0]); + flecs_fini_root_tables(world, idr, true); + flecs_defer_end(world, &world->stages[0]); + + flecs_defer_begin(world, &world->stages[0]); + flecs_fini_root_tables(world, idr, false); + flecs_defer_end(world, &world->stages[0]); +} + +static +void flecs_fini_store(ecs_world_t *world) { + flecs_clean_tables(world); + flecs_sparse_fini(&world->store.tables); + flecs_table_free(world, &world->store.root); + flecs_entities_clear(world); + flecs_hashmap_fini(&world->store.table_map); + + ecs_allocator_t *a = &world->allocator; + ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); + ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); + ecs_vec_fini_t(a, &world->store.depth_ids, ecs_entity_t); + ecs_map_fini(&world->store.entity_to_depth); +} + +/* Implementation for iterable mixin */ +static +bool flecs_world_iter_next( + ecs_iter_t *it) +{ + if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + ecs_iter_fini(it); + return false; + } + + ecs_world_t *world = it->real_world; + it->entities = ECS_CONST_CAST(ecs_entity_t*, flecs_entities_ids(world)); + it->count = flecs_entities_count(world); + flecs_iter_validate(it); + + return true; +} + +static +void flecs_world_iter_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) +{ + ecs_poly_assert(poly, ecs_world_t); + (void)poly; + + if (filter) { + iter[0] = ecs_term_iter(world, filter); + } else { + iter[0] = (ecs_iter_t){ + .world = ECS_CONST_CAST(ecs_world_t*, world), + .real_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)), + .next = flecs_world_iter_next + }; + } +} + +static +void flecs_world_allocators_init( + ecs_world_t *world) +{ + ecs_world_allocators_t *a = &world->allocators; + + flecs_allocator_init(&world->allocator); + + ecs_map_params_init(&a->ptr, &world->allocator); + ecs_map_params_init(&a->query_table_list, &world->allocator); + + flecs_ballocator_init_t(&a->query_table, ecs_query_table_t); + flecs_ballocator_init_t(&a->query_table_match, ecs_query_table_match_t); + flecs_ballocator_init_n(&a->graph_edge_lo, ecs_graph_edge_t, FLECS_HI_COMPONENT_ID); + flecs_ballocator_init_t(&a->graph_edge, ecs_graph_edge_t); + flecs_ballocator_init_t(&a->id_record, ecs_id_record_t); + flecs_ballocator_init_n(&a->id_record_chunk, ecs_id_record_t, FLECS_SPARSE_PAGE_SIZE); + flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); + flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_PAGE_SIZE); + flecs_ballocator_init_t(&a->hashmap, ecs_hashmap_t); + flecs_table_diff_builder_init(world, &world->allocators.diff_builder); +} + +static +void flecs_world_allocators_fini( + ecs_world_t *world) +{ + ecs_world_allocators_t *a = &world->allocators; + + ecs_map_params_fini(&a->ptr); + ecs_map_params_fini(&a->query_table_list); + + flecs_ballocator_fini(&a->query_table); + flecs_ballocator_fini(&a->query_table_match); + flecs_ballocator_fini(&a->graph_edge_lo); + flecs_ballocator_fini(&a->graph_edge); + flecs_ballocator_fini(&a->id_record); + flecs_ballocator_fini(&a->id_record_chunk); + flecs_ballocator_fini(&a->table_diff); + flecs_ballocator_fini(&a->sparse_chunk); + flecs_ballocator_fini(&a->hashmap); + flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); + + flecs_allocator_fini(&world->allocator); +} + +static +void flecs_log_addons(void) { + ecs_trace("addons included in build:"); + ecs_log_push(); + #ifdef FLECS_CPP + ecs_trace("FLECS_CPP"); + #endif + #ifdef FLECS_MODULE + ecs_trace("FLECS_MODULE"); + #endif + #ifdef FLECS_PARSER + ecs_trace("FLECS_PARSER"); + #endif + #ifdef FLECS_PLECS + ecs_trace("FLECS_PLECS"); + #endif + #ifdef FLECS_RULES + ecs_trace("FLECS_RULES"); + #endif + #ifdef FLECS_SNAPSHOT + ecs_trace("FLECS_SNAPSHOT"); + #endif + #ifdef FLECS_STATS + ecs_trace("FLECS_STATS"); + #endif + #ifdef FLECS_MONITOR + ecs_trace("FLECS_MONITOR"); + #endif + #ifdef FLECS_METRICS + ecs_trace("FLECS_METRICS"); + #endif + #ifdef FLECS_SYSTEM + ecs_trace("FLECS_SYSTEM"); + #endif + #ifdef FLECS_PIPELINE + ecs_trace("FLECS_PIPELINE"); + #endif + #ifdef FLECS_TIMER + ecs_trace("FLECS_TIMER"); + #endif + #ifdef FLECS_META + ecs_trace("FLECS_META"); + #endif + #ifdef FLECS_META_C + ecs_trace("FLECS_META_C"); + #endif + #ifdef FLECS_UNITS + ecs_trace("FLECS_UNITS"); + #endif + #ifdef FLECS_EXPR + ecs_trace("FLECS_EXPR"); + #endif + #ifdef FLECS_JSON + ecs_trace("FLECS_JSON"); + #endif + #ifdef FLECS_DOC + ecs_trace("FLECS_DOC"); + #endif + #ifdef FLECS_COREDOC + ecs_trace("FLECS_COREDOC"); + #endif + #ifdef FLECS_LOG + ecs_trace("FLECS_LOG"); + #endif + #ifdef FLECS_JOURNAL + ecs_trace("FLECS_JOURNAL"); + #endif + #ifdef FLECS_APP + ecs_trace("FLECS_APP"); + #endif + #ifdef FLECS_OS_API_IMPL + ecs_trace("FLECS_OS_API_IMPL"); + #endif + #ifdef FLECS_SCRIPT + ecs_trace("FLECS_SCRIPT"); + #endif + #ifdef FLECS_HTTP + ecs_trace("FLECS_HTTP"); + #endif + #ifdef FLECS_REST + ecs_trace("FLECS_REST"); + #endif + ecs_log_pop(); +} + +/* -- Public functions -- */ + +ecs_world_t *ecs_mini(void) { +#ifdef FLECS_OS_API_IMPL + ecs_set_os_api_impl(); +#endif + ecs_os_init(); + + ecs_trace("#[bold]bootstrapping world"); + ecs_log_push(); + + ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); + + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); + } + + if (!ecs_os_has_threading()) { + ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); + } + + if (!ecs_os_has_time()) { + ecs_trace("time management not available"); + } + + flecs_log_addons(); + +#ifdef FLECS_SANITIZE + ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " + "improved performance"); +#elif defined(FLECS_DEBUG) + ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " + "performance"); +#else + ecs_trace("#[green]release#[reset] build"); +#endif + +#ifdef __clang__ + ecs_trace("compiled with clang %s", __clang_version__); +#elif defined(__GNUC__) + ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); +#elif defined (_MSC_VER) + ecs_trace("compiled with msvc %d", _MSC_VER); +#elif defined (__TINYC__) + ecs_trace("compiled with tcc %d", __TINYC__); +#endif + + ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); + ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_poly_init(world, ecs_world_t); + + world->flags |= EcsWorldInit; + + flecs_world_allocators_init(world); + ecs_allocator_t *a = &world->allocator; + + world->self = world; + flecs_sparse_init_t(&world->type_info, a, + &world->allocators.sparse_chunk, ecs_type_info_t); + ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr); + world->id_index_lo = ecs_os_calloc_n(ecs_id_record_t, FLECS_HI_ID_RECORD_ID); + flecs_observable_init(&world->observable); + world->iterable.init = flecs_world_iter_init; + + world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); + flecs_sparse_init_t(world->pending_tables, a, + &world->allocators.sparse_chunk, ecs_table_t*); + world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); + flecs_sparse_init_t(world->pending_buffer, a, + &world->allocators.sparse_chunk, ecs_table_t*); + + flecs_name_index_init(&world->aliases, a); + flecs_name_index_init(&world->symbols, a); + ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); + + world->info.time_scale = 1.0; + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); + } + + ecs_set_stage_count(world, 1); + ecs_default_lookup_path[0] = EcsFlecsCore; + ecs_set_lookup_path(world, ecs_default_lookup_path); + flecs_init_store(world); + + flecs_bootstrap(world); + + world->flags &= ~EcsWorldInit; + + ecs_trace("world ready!"); + ecs_log_pop(); + + return world; +} + +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); + +#ifdef FLECS_MODULE_H + ecs_trace("#[bold]import addons"); + ecs_log_push(); + ecs_trace("use ecs_mini to create world without importing addons"); +#ifdef FLECS_SYSTEM + ECS_IMPORT(world, FlecsSystem); +#endif +#ifdef FLECS_PIPELINE + ECS_IMPORT(world, FlecsPipeline); +#endif +#ifdef FLECS_TIMER + ECS_IMPORT(world, FlecsTimer); +#endif +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); +#endif +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsCoreDoc); +#endif +#ifdef FLECS_SCRIPT + ECS_IMPORT(world, FlecsScript); +#endif +#ifdef FLECS_REST + ECS_IMPORT(world, FlecsRest); +#endif +#ifdef FLECS_UNITS + ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); +#endif + ecs_trace("addons imported!"); + ecs_log_pop(); +#endif + return world; +} + +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) +{ + ecs_world_t *world = ecs_init(); + + (void)argc; + (void)argv; + +#ifdef FLECS_DOC + if (argc) { + char *app = argv[0]; + char *last_elem = strrchr(app, '/'); + if (!last_elem) { + last_elem = strrchr(app, '\\'); + } + if (last_elem) { + app = last_elem + 1; + } + ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); + } +#endif + + return world; +} + +void ecs_quit( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + world->flags |= EcsWorldQuit; +error: + return; +} + +bool ecs_should_quit( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +error: + return true; +} + +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event) +{ + ecs_poly_assert(world, ecs_world_t); + + /* If no id is specified, broadcast to all tables */ + if (!id) { + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + flecs_table_notify(world, table, event); + } + + /* If id is specified, only broadcast to tables with id */ + } else { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return; + } + + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; + + flecs_table_cache_all_iter(&idr->cache, &it); + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + flecs_table_notify(world, tr->hdr.table, event); + } + } +} + +void ecs_default_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ti) +{ + ecs_os_memset(ptr, 0, ti->size * count); +} + +static +void flecs_default_copy_ctor(void *dst_ptr, const void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->copy(dst_ptr, src_ptr, count, ti); +} + +static +void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->move(dst_ptr, src_ptr, count, ti); +} + +static +void flecs_default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->move(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); +} + +static +void flecs_default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move_ctor(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); +} + +static +void flecs_default_move(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move(dst_ptr, src_ptr, count, ti); +} + +static +void flecs_default_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + /* When there is no move, destruct the destination component & memcpy the + * component to dst. The src component does not have to be destructed when + * a component has a trivial move. */ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->dtor(dst_ptr, count, ti); + ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); +} + +static +void flecs_default_move_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + /* If a component has a move, the move will take care of memcpying the data + * and destroying any data in dst. Because this is not a trivial move, the + * src component must also be destructed. */ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); +} + +void ecs_set_hooks_id( + ecs_world_t *world, + ecs_entity_t component, + const ecs_type_hooks_t *h) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_stage_from_world(&world); + + /* Ensure that no tables have yet been created for the component */ + ecs_assert( ecs_id_in_use(world, component) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); + ecs_assert( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); + + ecs_type_info_t *ti = flecs_type_info_ensure(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_check(!ti->component || ti->component == component, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + + if (!ti->size) { + const EcsComponent *component_ptr = ecs_get( + world, component, EcsComponent); + + /* Cannot register lifecycle actions for things that aren't a component */ + ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + /* Cannot register lifecycle actions for components with size 0 */ + ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); + + ti->size = component_ptr->size; + ti->alignment = component_ptr->alignment; + } + + if (h->ctor) ti->hooks.ctor = h->ctor; + if (h->dtor) ti->hooks.dtor = h->dtor; + if (h->copy) ti->hooks.copy = h->copy; + if (h->move) ti->hooks.move = h->move; + if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; + if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; + if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; + if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; + + if (h->on_add) ti->hooks.on_add = h->on_add; + if (h->on_remove) ti->hooks.on_remove = h->on_remove; + if (h->on_set) ti->hooks.on_set = h->on_set; + + if (h->ctx) ti->hooks.ctx = h->ctx; + if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; + if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; + if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; + + /* 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 (!h->ctor && (h->dtor || h->copy || h->move)) { + ti->hooks.ctor = ecs_default_ctor; + } + + /* Set default copy ctor, move ctor and merge */ + if (h->copy && !h->copy_ctor) { + ti->hooks.copy_ctor = flecs_default_copy_ctor; + } + + if (h->move && !h->move_ctor) { + ti->hooks.move_ctor = flecs_default_move_ctor; + } + + if (!h->ctor_move_dtor) { + if (h->move) { + if (h->dtor) { + if (h->move_ctor) { + /* If an explicit move ctor has been set, use callback + * that uses the move ctor vs. using a ctor+move */ + ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; + } else { + /* If no explicit move_ctor has been set, use + * combination of ctor + move + dtor */ + ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; + } + } else { + /* If no dtor has been set, this is just a move ctor */ + ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; + } + } else { + /* If move is not set but move_ctor and dtor is, we can still set + * ctor_move_dtor. */ + if (h->move_ctor) { + if (h->dtor) { + ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; + } else { + ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; + } + } + } + } + + if (!h->move_dtor) { + if (h->move) { + if (h->dtor) { + ti->hooks.move_dtor = flecs_default_move_w_dtor; + } else { + ti->hooks.move_dtor = flecs_default_move; + } + } else { + if (h->dtor) { + ti->hooks.move_dtor = flecs_default_dtor; + } + } + } + +error: + return; +} + +const ecs_type_hooks_t* ecs_get_hooks_id( + ecs_world_t *world, + ecs_entity_t id) +{ + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + return &ti->hooks; + } + return NULL; +} + +void ecs_atfini( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_action_elem_t *elem = ecs_vec_append_t(NULL, &world->fini_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +error: + return; +} + +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, + &stage->post_frame_actions, ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +error: + return; +} + +/* Unset data in tables */ +static +void flecs_fini_unset_tables( + ecs_world_t *world) +{ + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + flecs_table_remove_actions(world, table); + } +} + +/* Invoke fini actions */ +static +void flecs_fini_actions( + ecs_world_t *world) +{ + int32_t i, count = ecs_vec_count(&world->fini_actions); + ecs_action_elem_t *elems = ecs_vec_first(&world->fini_actions); + for (i = 0; i < count; i ++) { + elems[i].action(world, elems[i].ctx); + } + + ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); +} + +/* Cleanup remaining type info elements */ +static +void flecs_fini_type_info( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(&world->type_info); + ecs_sparse_t *type_info = &world->type_info; + for (i = 0; i < count; i ++) { + ecs_type_info_t *ti = flecs_sparse_get_dense_t(type_info, + ecs_type_info_t, i); + flecs_type_info_fini(ti); + } + flecs_sparse_fini(&world->type_info); +} + +ecs_entity_t flecs_get_oneof( + const ecs_world_t *world, + ecs_entity_t e) +{ + if (ecs_is_alive(world, e)) { + if (ecs_has_id(world, e, EcsOneOf)) { + return e; + } else { + return ecs_get_target(world, e, EcsOneOf, 0); + } + } else { + return 0; + } +} + +/* The destroyer of worlds */ +int ecs_fini( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); + ecs_assert(world->stages[0].defer == 0, ECS_INVALID_OPERATION, + "call defer_end before destroying world"); + + ecs_trace("#[bold]shutting down world"); + ecs_log_push(); + + world->flags |= EcsWorldQuit; + + /* Delete root entities first using regular APIs. This ensures that cleanup + * policies get a chance to execute. */ + ecs_dbg_1("#[bold]cleanup root entities"); + ecs_log_push_1(); + flecs_fini_roots(world); + ecs_log_pop_1(); + + world->flags |= EcsWorldFini; + + /* Run fini actions (simple callbacks ran when world is deleted) before + * destroying the storage */ + ecs_dbg_1("#[bold]run fini actions"); + ecs_log_push_1(); + flecs_fini_actions(world); + ecs_log_pop_1(); + + ecs_dbg_1("#[bold]cleanup remaining entities"); + ecs_log_push_1(); + + /* Operations invoked during UnSet/OnRemove/destructors are deferred and + * will be discarded after world cleanup */ + flecs_defer_begin(world, &world->stages[0]); + + /* Run UnSet/OnRemove actions for components while the store is still + * unmodified by cleanup. */ + flecs_fini_unset_tables(world); + + /* This will destroy all entities and components. */ + flecs_fini_store(world); + + /* Purge deferred operations from the queue. This discards operations but + * makes sure that any resources in the queue are freed */ + flecs_defer_purge(world, &world->stages[0]); + ecs_log_pop_1(); + + /* All queries are cleaned up, so monitors should've been cleaned up too */ + ecs_assert(!ecs_map_is_init(&world->monitors.monitors), + ECS_INTERNAL_ERROR, NULL); + + /* Cleanup world ctx and binding_ctx */ + if (world->ctx_free) { + world->ctx_free(world->ctx); + } + if (world->binding_ctx_free) { + world->binding_ctx_free(world->binding_ctx); + } + + /* After this point no more user code is invoked */ + + ecs_dbg_1("#[bold]cleanup world data structures"); + ecs_log_push_1(); + flecs_entities_fini(world); + flecs_sparse_fini(world->pending_tables); + flecs_sparse_fini(world->pending_buffer); + ecs_os_free(world->pending_tables); + ecs_os_free(world->pending_buffer); + flecs_fini_id_records(world); + flecs_fini_type_info(world); + flecs_observable_fini(&world->observable); + flecs_name_index_fini(&world->aliases); + flecs_name_index_fini(&world->symbols); + ecs_set_stage_count(world, 0); + ecs_log_pop_1(); + + flecs_world_allocators_fini(world); + + /* End of the world */ + ecs_poly_free(world, ecs_world_t); + ecs_os_fini(); + + ecs_trace("world destroyed, bye!"); + ecs_log_pop(); + + return 0; +} + +bool ecs_is_fini( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return ECS_BIT_IS_SET(world->flags, EcsWorldFini); +} + +void ecs_dim( + ecs_world_t *world, + int32_t entity_count) +{ + ecs_poly_assert(world, ecs_world_t); + flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); +} + +void flecs_eval_component_monitors( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + flecs_process_pending_tables(world); + flecs_eval_component_monitor(world); +} + +void ecs_measure_frame_time( + ecs_world_t *world, + bool enable) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (ECS_EQZERO(world->info.target_fps) || enable) { + ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); + } +error: + return; +} + +void ecs_measure_system_time( + ecs_world_t *world, + bool enable) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); +error: + return; +} + +void ecs_set_target_fps( + ecs_world_t *world, + ecs_ftime_t fps) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + ecs_measure_frame_time(world, true); + world->info.target_fps = fps; +error: + return; +} + +void* ecs_get_ctx( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->ctx; +error: + return NULL; +} + +void* ecs_get_binding_ctx( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->binding_ctx; +error: + return NULL; +} + +void ecs_set_ctx( + ecs_world_t *world, + void *ctx, + ecs_ctx_free_t ctx_free) +{ + ecs_poly_assert(world, ecs_world_t); + world->ctx = ctx; + world->ctx_free = ctx_free; +} + +void ecs_set_binding_ctx( + ecs_world_t *world, + void *ctx, + ecs_ctx_free_t ctx_free) +{ + ecs_poly_assert(world, ecs_world_t); + world->binding_ctx = ctx; + world->binding_ctx_free = ctx_free; +} + +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); + ecs_check(!id_end || id_end > flecs_entities_max_id(world), + ECS_INVALID_PARAMETER, NULL); + + uint32_t start = (uint32_t)id_start; + uint32_t end = (uint32_t)id_end; + + if (flecs_entities_max_id(world) < start) { + flecs_entities_max_id(world) = start - 1; + } + + world->info.min_id = start; + world->info.max_id = end; +error: + return; +} + +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable) +{ + ecs_poly_assert(world, ecs_world_t); + bool old_value = world->range_check_enabled; + world->range_check_enabled = enable; + return old_value; +} + +ecs_entity_t ecs_get_max_id( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return flecs_entities_max_id(world); +error: + return 0; +} + +void ecs_set_entity_generation( + ecs_world_t *world, + ecs_entity_t entity_with_generation) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, NULL); + + flecs_entities_set_generation(world, entity_with_generation); + + ecs_record_t *r = flecs_entities_get(world, entity_with_generation); + if (r && r->table) { + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_entity_t *entities = r->table->data.entities.array; + entities[row] = entity_with_generation; + } +} + +const ecs_type_info_t* flecs_type_info_get( + const ecs_world_t *world, + ecs_entity_t component) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); + + return flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); +} + +ecs_type_info_t* flecs_type_info_ensure( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + + const ecs_type_info_t *ti = flecs_type_info_get(world, component); + ecs_type_info_t *ti_mut = NULL; + if (!ti) { + ti_mut = flecs_sparse_ensure_t( + &world->type_info, ecs_type_info_t, component); + ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); + ti_mut->component = component; + } else { + ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); + } + + if (!ti_mut->name) { + const char *sym = ecs_get_symbol(world, component); + if (sym) { + ti_mut->name = ecs_os_strdup(sym); + } else { + const char *name = ecs_get_name(world, component); + if (name) { + ti_mut->name = ecs_os_strdup(name); + } + } + } + + return ti_mut; +} + +bool flecs_type_info_init_id( + ecs_world_t *world, + ecs_entity_t component, + ecs_size_t size, + ecs_size_t alignment, + const ecs_type_hooks_t *li) +{ + bool changed = false; + + flecs_entities_ensure(world, component); + + ecs_type_info_t *ti = NULL; + if (!size || !alignment) { + ecs_assert(size == 0 && alignment == 0, + ECS_INVALID_COMPONENT_SIZE, NULL); + ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); + } else { + ti = flecs_type_info_ensure(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + changed |= ti->size != size; + changed |= ti->alignment != alignment; + ti->size = size; + ti->alignment = alignment; + if (li) { + ecs_set_hooks_id(world, component, li); + } + } + + /* Set type info for id record of component */ + ecs_id_record_t *idr = flecs_id_record_ensure(world, component); + changed |= flecs_id_record_set_type_info(world, idr, ti); + bool is_tag = idr->flags & EcsIdTag; + + /* All id records with component as relationship inherit type info */ + idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); + do { + if (is_tag) { + changed |= flecs_id_record_set_type_info(world, idr, NULL); + } else if (ti) { + changed |= flecs_id_record_set_type_info(world, idr, ti); + } else if ((idr->type_info != NULL) && + (idr->type_info->component == component)) + { + changed |= flecs_id_record_set_type_info(world, idr, NULL); + } + } while ((idr = idr->first.next)); + + /* All non-tag id records with component as object inherit type info, + * if relationship doesn't have type info */ + idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); + do { + if (!(idr->flags & EcsIdTag) && !idr->type_info) { + changed |= flecs_id_record_set_type_info(world, idr, ti); + } + } while ((idr = idr->first.next)); + + /* Type info of (*, component) should always point to component */ + ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> + type_info == ti, ECS_INTERNAL_ERROR, NULL); + + return changed; +} + +void flecs_type_info_fini( + ecs_type_info_t *ti) +{ + if (ti->hooks.ctx_free) { + ti->hooks.ctx_free(ti->hooks.ctx); + } + if (ti->hooks.binding_ctx_free) { + ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); + } + if (ti->name) { + /* Safe to cast away const, world has ownership over string */ + ecs_os_free(ECS_CONST_CAST(char*, ti->name)); + ti->name = NULL; + } +} + +void flecs_type_info_free( + ecs_world_t *world, + ecs_entity_t component) +{ + if (world->flags & EcsWorldQuit) { + /* If world is in the final teardown stages, cleanup policies are no + * longer applied and it can't be guaranteed that a component is not + * deleted before entities that use it. The remaining type info elements + * will be deleted after the store is finalized. */ + return; + } + + ecs_type_info_t *ti = flecs_sparse_try_t(&world->type_info, + ecs_type_info_t, component); + if (ti) { + flecs_type_info_fini(ti); + flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); + } +} + +static +ecs_ftime_t flecs_insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_time_t start = *stop, now = start; + ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); + + if (ECS_EQZERO(world->info.target_fps)) { + return delta_time; + } + + ecs_ftime_t target_delta_time = + ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); + + /* Calculate the time we need to sleep by taking the measured delta from the + * previous frame, and subtracting it from target_delta_time. */ + ecs_ftime_t sleep = target_delta_time - delta_time; + + /* Pick a sleep interval that is 4 times smaller than the time one frame + * should take. */ + ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.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 (ECS_NEQZERO(sleep_time)) { + ecs_sleepf((double)sleep_time); + } + + now = start; + delta_time = (ecs_ftime_t)ecs_time_measure(&now); + } while ((target_delta_time - delta_time) > + (sleep_time / (ecs_ftime_t)2.0)); + + *stop = now; + return delta_time; +} + +static +ecs_ftime_t flecs_start_measure_frame( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_ftime_t delta_time = 0; + + if ((world->flags & EcsWorldMeasureFrameTime) || + (ECS_EQZERO(user_delta_time))) + { + ecs_time_t t = world->frame_start_time; + do { + if (world->frame_start_time.nanosec || world->frame_start_time.sec){ + delta_time = flecs_insert_sleep(world, &t); + } else { + ecs_time_measure(&t); + if (ECS_NEQZERO(world->info.target_fps)) { + delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; + } else { + /* Best guess */ + delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; + } + } + + /* Keep trying while delta_time is zero */ + } while (ECS_EQZERO(delta_time)); + + world->frame_start_time = t; + + /* Keep track of total time passed in world */ + world->info.world_time_total_raw += (ecs_ftime_t)delta_time; + } + + return (ecs_ftime_t)delta_time; +} + +static +void flecs_stop_measure_frame( + ecs_world_t* world) +{ + ecs_poly_assert(world, ecs_world_t); + + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_t t = world->frame_start_time; + world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); + } +} + +ecs_ftime_t ecs_frame_begin( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), + ECS_MISSING_OS_API, "get_time"); + + /* Start measuring total frame time */ + ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); + if (ECS_EQZERO(user_delta_time)) { + user_delta_time = delta_time; + } + + world->info.delta_time_raw = user_delta_time; + world->info.delta_time = user_delta_time * world->info.time_scale; + + /* Keep track of total scaled time passed in world */ + world->info.world_time_total += world->info.delta_time; + + ecs_run_aperiodic(world, 0); + + return world->info.delta_time; +error: + return (ecs_ftime_t)0; +} + +void ecs_frame_end( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + + world->info.frame_count_total ++; + + ecs_stage_t *stages = world->stages; + int32_t i, count = world->stage_count; + for (i = 0; i < count; i ++) { + flecs_stage_merge_post_frame(world, &stages[i]); + } + + flecs_stop_measure_frame(world); +error: + return; +} + +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return &world->info; +} + +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_poly_assert(world, ecs_world_t); + flecs_table_free(world, table); +} + +static +void flecs_process_empty_queries( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(ecs_id(EcsPoly), EcsQuery)); + if (!idr) { + return; + } + + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + flecs_defer_begin(world, &world->stages[0]); + + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; + if (flecs_table_cache_iter(&idr->cache, &it)) { + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); + int32_t i, count = ecs_table_count(table); + + for (i = 0; i < count; i ++) { + ecs_query_t *query = queries[i].poly; + ecs_entity_t *entities = table->data.entities.array; + if (!ecs_query_table_count(query)) { + ecs_add_id(world, entities[i], EcsEmpty); + } + } + } + } + + flecs_defer_end(world, &world->stages[0]); +} + +/** Walk over tables that had a state change which requires bookkeeping */ +void flecs_process_pending_tables( + const ecs_world_t *world_r) +{ + ecs_poly_assert(world_r, ecs_world_t); + + /* We can't update the administration while in readonly mode, but we can + * ensure that when this function is called there are no pending events. */ + if (world_r->flags & EcsWorldReadonly) { + ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, + ECS_INTERNAL_ERROR, NULL); + return; + } + + /* Safe to cast, world is not readonly */ + ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); + + /* If pending buffer is NULL there already is a stackframe that's iterating + * the table list. This can happen when an observer for a table event results + * in a mutation that causes another table to change state. A typical + * example of this is a system that becomes active/inactive as the result of + * a query (and as a result, its matched tables) becoming empty/non empty */ + if (!world->pending_buffer) { + return; + } + + /* Swap buffer. The logic could in theory have been implemented with a + * single sparse set, but that would've complicated (and slowed down) the + * iteration. Additionally, by using a double buffer approach we can still + * keep most of the original ordering of events intact, which is desirable + * as it means that the ordering of tables in the internal data structures is + * more predictable. */ + int32_t i, count = flecs_sparse_count(world->pending_tables); + if (!count) { + return; + } + + flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); + + do { + ecs_sparse_t *pending_tables = world->pending_tables; + world->pending_tables = world->pending_buffer; + world->pending_buffer = NULL; + + /* Make sure that any ECS operations that occur while delivering the + * events does not cause inconsistencies, like sending an Empty + * notification for a table that just became non-empty. */ + flecs_defer_begin(world, &world->stages[0]); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t( + pending_tables, ecs_table_t*, i)[0]; + if (!table->id) { + /* Table is being deleted, ignore empty events */ + continue; + } + + /* For each id in the table, add it to the empty/non empty list + * based on its current state */ + if (flecs_table_records_update_empty(table)) { + int32_t table_count = ecs_table_count(table); + if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { + /* Only emit an event when there was a change in the + * administration. It is possible that a table ended up in the + * pending_tables list by going from empty->non-empty, but then + * became empty again. By the time we run this code, no changes + * in the administration would actually be made. */ + ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; + if (ecs_should_log_3()) { + ecs_dbg_3("table %u state change (%s)", + (uint32_t)table->id, + table_count ? "non-empty" : "empty"); + } + + ecs_log_push_3(); + + flecs_emit(world, world, &(ecs_event_desc_t){ + .event = evt, + .table = table, + .ids = &table->type, + .observable = world, + .flags = EcsEventTableOnly + }); + + ecs_log_pop_3(); + } + world->info.empty_table_count += (table_count == 0) * 2 - 1; + } + } + + flecs_sparse_clear(pending_tables); + ecs_defer_end(world); + + world->pending_buffer = pending_tables; + } while ((count = flecs_sparse_count(world->pending_tables))); + + flecs_journal_end(); +} + +void flecs_table_set_empty( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_table_count(table)) { + table->_->generation = 0; + } + + flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, + (uint32_t)table->id)[0] = table; +} + +bool ecs_id_in_use( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return false; + } + return (flecs_table_cache_count(&idr->cache) != 0) || + (flecs_table_cache_empty_count(&idr->cache) != 0); +} + +void ecs_run_aperiodic( + ecs_world_t *world, + ecs_flags32_t flags) +{ + ecs_poly_assert(world, ecs_world_t); + + if (!flags || (flags & EcsAperiodicEmptyTables)) { + flecs_process_pending_tables(world); + } + if ((flags & EcsAperiodicEmptyQueries)) { + flecs_process_empty_queries(world); + } + if (!flags || (flags & EcsAperiodicComponentMonitors)) { + flecs_eval_component_monitors(world); + } +} + +int32_t ecs_delete_empty_tables( + ecs_world_t *world, + ecs_id_t id, + uint16_t clear_generation, + uint16_t delete_generation, + int32_t min_id_count, + double time_budget_seconds) +{ + ecs_poly_assert(world, ecs_world_t); + + /* Make sure empty tables are in the empty table lists */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + ecs_time_t start = {0}, cur = {0}; + int32_t delete_count = 0, clear_count = 0; + bool time_budget = false; + + if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { + ecs_time_measure(&start); + } + + if (ECS_NEQZERO(time_budget_seconds)) { + time_budget = true; + } + + if (!id) { + id = EcsAny; /* Iterate all empty tables */ + } + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + if (time_budget) { + cur = start; + if (ecs_time_measure(&cur) > time_budget_seconds) { + goto done; + } + } + + ecs_table_t *table = tr->hdr.table; + ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); + + if (table->type.count < min_id_count) { + continue; + } + + uint16_t gen = ++ table->_->generation; + if (delete_generation && (gen > delete_generation)) { + flecs_table_free(world, table); + delete_count ++; + } else if (clear_generation && (gen > clear_generation)) { + if (flecs_table_shrink(world, table)) { + clear_count ++; + } + } + } + } + +done: + if (ecs_should_log_1() && ecs_os_has_time()) { + if (delete_count) { + ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", + delete_count, ecs_time_measure(&start)); + } + if (clear_count) { + ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", + clear_count, ecs_time_measure(&start)); + } + } + + return delete_count; +} + +/** + * @file addons/alerts.c + * @brief Alerts addon. + */ + + +#ifdef FLECS_ALERTS + +ECS_COMPONENT_DECLARE(FlecsAlerts); + +typedef struct EcsAlert { + char *message; + ecs_map_t instances; /* Active instances for metric */ + ecs_ftime_t retain_period; /* How long to retain the alert */ + ecs_vec_t severity_filters; /* Severity filters */ + + /* Member range monitoring */ + ecs_id_t id; /* (Component) id that contains to monitor member */ + ecs_entity_t member; /* Member to monitor */ + int32_t offset; /* Offset of member in component */ + int32_t size; /* Size of component */ + ecs_primitive_kind_t kind; /* Primitive type kind */ + ecs_ref_t ranges; /* Reference to ranges component */ + int32_t var_id; /* Variable from which to obtain data (0 = $this) */ +} EcsAlert; + +typedef struct EcsAlertTimeout { + ecs_ftime_t inactive_time; /* Time the alert has been inactive */ + ecs_ftime_t expire_time; /* Expiration duration */ +} EcsAlertTimeout; + +ECS_COMPONENT_DECLARE(EcsAlertTimeout); + +static +ECS_CTOR(EcsAlert, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->instances, NULL); + ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0); +}) + +static +ECS_DTOR(EcsAlert, ptr, { + ecs_os_free(ptr->message); + ecs_map_fini(&ptr->instances); + ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t); +}) + +static +ECS_MOVE(EcsAlert, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; + + ecs_map_fini(&dst->instances); + dst->instances = src->instances; + src->instances = (ecs_map_t){0}; + + ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t); + dst->severity_filters = src->severity_filters; + src->severity_filters = (ecs_vec_t){0}; + + dst->retain_period = src->retain_period; + dst->id = src->id; + dst->member = src->member; + dst->offset = src->offset; + dst->size = src->size; + dst->kind = src->kind; + dst->ranges = src->ranges; + dst->var_id = src->var_id; +}) + +static +ECS_CTOR(EcsAlertsActive, ptr, { + ecs_map_init(&ptr->alerts, NULL); + ptr->info_count = 0; + ptr->warning_count = 0; + ptr->error_count = 0; +}) + +static +ECS_DTOR(EcsAlertsActive, ptr, { + ecs_map_fini(&ptr->alerts); +}) + +static +ECS_MOVE(EcsAlertsActive, dst, src, { + ecs_map_fini(&dst->alerts); + dst->alerts = src->alerts; + dst->info_count = src->info_count; + dst->warning_count = src->warning_count; + dst->error_count = src->error_count; + src->alerts = (ecs_map_t){0}; +}) + +static +ECS_DTOR(EcsAlertInstance, ptr, { + ecs_os_free(ptr->message); +}) + +static +ECS_MOVE(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; +}) + +static +ECS_COPY(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = ecs_os_strdup(src->message); +}) + +static +void flecs_alerts_add_alert_to_src( + ecs_world_t *world, + ecs_entity_t source, + ecs_entity_t alert, + ecs_entity_t alert_instance) +{ + EcsAlertsActive *active = ecs_get_mut( + world, source, EcsAlertsActive); + ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); + if (severity == EcsAlertInfo) { + active->info_count ++; + } else if (severity == EcsAlertWarning) { + active->warning_count ++; + } else if (severity == EcsAlertError) { + active->error_count ++; + } + + ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ptr[0] = alert_instance; + ecs_modified(world, source, EcsAlertsActive); +} + +static +void flecs_alerts_remove_alert_from_src( + ecs_world_t *world, + ecs_entity_t source, + ecs_entity_t alert) +{ + EcsAlertsActive *active = ecs_get_mut( + world, source, EcsAlertsActive); + ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_remove(&active->alerts, alert); + + ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); + if (severity == EcsAlertInfo) { + active->info_count --; + } else if (severity == EcsAlertWarning) { + active->warning_count --; + } else if (severity == EcsAlertError) { + active->error_count --; + } + + if (!ecs_map_count(&active->alerts)) { + ecs_remove(world, source, EcsAlertsActive); + } else { + ecs_modified(world, source, EcsAlertsActive); + } +} + +static +ecs_entity_t flecs_alert_get_severity( + ecs_world_t *world, + ecs_iter_t *it, + EcsAlert *alert) +{ + int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); + ecs_alert_severity_filter_t *filters = + ecs_vec_first(&alert->severity_filters); + for (i = 0; i < filter_count; i ++) { + ecs_alert_severity_filter_t *filter = &filters[i]; + if (!filter->var) { + if (ecs_table_has_id(world, it->table, filters[i].with)) { + return filters[i].severity; + } + } else { + ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); + if (src && src != EcsWildcard) { + if (ecs_has_id(world, src, filters[i].with)) { + return filters[i].severity; + } + } + } + } + + return 0; +} + +static +ecs_entity_t flecs_alert_out_of_range_kind( + EcsAlert *alert, + const EcsMemberRanges *ranges, + const void *value_ptr) +{ + double value = 0; + + switch(alert->kind) { + case EcsU8: value = *(const uint8_t*)value_ptr; break; + case EcsU16: value = *(const uint16_t*)value_ptr; break; + case EcsU32: value = *(const uint32_t*)value_ptr; break; + case EcsU64: value = (double)*(const uint64_t*)value_ptr; break; + case EcsI8: value = *(const int8_t*)value_ptr; break; + case EcsI16: value = *(const int16_t*)value_ptr; break; + case EcsI32: value = *(const int32_t*)value_ptr; break; + case EcsI64: value = (double)*(const int64_t*)value_ptr; break; + case EcsF32: value = (double)*(const float*)value_ptr; break; + case EcsF64: value = *(const double*)value_ptr; break; + case EcsBool: + case EcsChar: + case EcsByte: + case EcsUPtr: + case EcsIPtr: + case EcsString: + case EcsEntity: + case EcsId: + return 0; + } + + bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); + bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); + + if (has_error && (value < ranges->error.min || value > ranges->error.max)) { + return EcsAlertError; + } else if (has_warning && + (value < ranges->warning.min || value > ranges->warning.max)) + { + return EcsAlertWarning; + } else { + return 0; + } +} + +static +void MonitorAlerts(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsAlert *alert = ecs_field(it, EcsAlert, 1); + EcsPoly *poly = ecs_field(it, EcsPoly, 2); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t a = it->entities[i]; /* Alert entity */ + ecs_entity_t default_severity = ecs_get_target( + world, a, ecs_id(EcsAlert), 0); + ecs_rule_t *rule = poly[i].poly; + ecs_poly_assert(rule, ecs_rule_t); + + ecs_id_t member_id = alert[i].id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges); + } + + ecs_iter_t rit = ecs_rule_iter(world, rule); + rit.flags |= EcsIterNoData; + rit.flags |= EcsIterIsInstanced; + + while (ecs_rule_next(&rit)) { + ecs_entity_t severity = flecs_alert_get_severity( + world, &rit, &alert[i]); + if (!severity) { + severity = default_severity; + } + + const void *member_data = NULL; + ecs_entity_t member_src = 0; + if (ranges) { + if (alert[i].var_id) { + member_src = ecs_iter_get_var(&rit, alert[i].var_id); + if (!member_src || member_src == EcsWildcard) { + continue; + } + } + if (!member_src) { + member_data = ecs_table_get_id( + world, rit.table, member_id, rit.offset); + } else { + member_data = ecs_get_id(world, member_src, member_id); + } + if (!member_data) { + continue; + } + member_data = ECS_OFFSET(member_data, alert[i].offset); + } + + int32_t j, alert_src_count = rit.count; + for (j = 0; j < alert_src_count; j ++) { + ecs_entity_t src_severity = severity; + ecs_entity_t e = rit.entities[j]; + if (member_data) { + ecs_entity_t range_severity = flecs_alert_out_of_range_kind( + &alert[i], ranges, member_data); + if (!member_src) { + member_data = ECS_OFFSET(member_data, alert[i].size); + } + if (!range_severity) { + continue; + } + if (range_severity < src_severity) { + /* Range severity should not exceed alert severity */ + src_severity = range_severity; + } + } + + ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e); + ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (!aptr[0]) { + /* Alert does not yet exist for entity */ + ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a); + ecs_set(world, ai, EcsAlertInstance, { .message = NULL }); + ecs_set(world, ai, EcsMetricSource, { .entity = e }); + ecs_set(world, ai, EcsMetricValue, { .value = 0 }); + ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity); + if (ECS_NEQZERO(alert[i].retain_period)) { + ecs_set(world, ai, EcsAlertTimeout, { + .inactive_time = 0, + .expire_time = alert[i].retain_period + }); + } + + ecs_defer_suspend(it->world); + flecs_alerts_add_alert_to_src(world, e, a, ai); + ecs_defer_resume(it->world); + aptr[0] = ai; + } else { + /* Make sure alert severity is up to date */ + if (ecs_vec_count(&alert[i].severity_filters) || member_data) { + ecs_entity_t cur_severity = ecs_get_target( + world, aptr[0], ecs_id(EcsAlert), 0); + if (cur_severity != src_severity) { + ecs_add_pair(world, aptr[0], ecs_id(EcsAlert), + src_severity); + } + } + } + } + } + } +} + +static +void MonitorAlertInstances(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 1); + EcsMetricSource *source = ecs_field(it, EcsMetricSource, 2); + EcsMetricValue *value = ecs_field(it, EcsMetricValue, 3); + EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 4); + + /* Get alert component from alert instance parent (the alert) */ + ecs_id_t childof_pair; + if (ecs_search(world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) { + ecs_err("alert instances must be a child of an alert"); + return; + } + ecs_entity_t parent = ecs_pair_second(world, childof_pair); + ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION, + "alert entity does not have Alert component"); + EcsAlert *alert = ecs_get_mut(world, parent, EcsAlert); + const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); + ecs_assert(poly != NULL, ECS_INVALID_OPERATION, + "alert entity does not have (Poly, Query) component"); + ecs_rule_t *rule = poly->poly; + ecs_poly_assert(rule, ecs_rule_t); + + ecs_id_t member_id = alert->id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); + } + + ecs_vars_t vars = {0}; + ecs_vars_init(world, &vars); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t ai = it->entities[i]; + ecs_entity_t e = source[i].entity; + + /* If source of alert is no longer alive, delete alert instance even if + * the alert has a retain period. */ + if (!ecs_is_alive(world, e)) { + ecs_delete(world, ai); + continue; + } + + /* Check if alert instance still matches rule */ + ecs_iter_t rit = ecs_rule_iter(world, rule); + rit.flags |= EcsIterNoData; + rit.flags |= EcsIterIsInstanced; + ecs_iter_set_var(&rit, 0, e); + + if (ecs_rule_next(&rit)) { + bool match = true; + + /* If alert is monitoring member range, test value against range */ + if (ranges) { + ecs_entity_t member_src = e; + if (alert->var_id) { + member_src = ecs_iter_get_var(&rit, alert->var_id); + } + + const void *member_data = ecs_get_id( + world, member_src, member_id); + if (!member_data) { + match = false; + } else { + member_data = ECS_OFFSET(member_data, alert->offset); + if (flecs_alert_out_of_range_kind( + alert, ranges, member_data) == 0) + { + match = false; + } + } + } + + if (match) { + /* Only increase alert duration if the alert was active */ + value[i].value += (double)it->delta_system_time; + + bool generate_message = alert->message; + if (generate_message) { + if (alert_instance[i].message) { + /* If a message was already generated, only regenerate if + * rule has multiple variables. Variable values could have + * changed, this ensures the message remains up to date. */ + generate_message = rit.variable_count > 1; + } + } + + if (generate_message) { + if (alert_instance[i].message) { + ecs_os_free(alert_instance[i].message); + } + + ecs_iter_to_vars(&rit, &vars, 0); + alert_instance[i].message = ecs_interpolate_string( + world, alert->message, &vars); + } + + if (timeout) { + if (ECS_NEQZERO(timeout[i].inactive_time)) { + /* The alert just became active. Remove Disabled tag */ + flecs_alerts_add_alert_to_src(world, e, parent, ai); + ecs_remove_id(world, ai, EcsDisabled); + } + timeout[i].inactive_time = 0; + } + + /* Alert instance still matches rule, keep it alive */ + ecs_iter_fini(&rit); + continue; + } + + ecs_iter_fini(&rit); + } + + /* Alert instance is no longer active */ + + if (timeout) { + if (ECS_EQZERO(timeout[i].inactive_time)) { + /* The alert just became inactive. Add Disabled tag */ + flecs_alerts_remove_alert_from_src(world, e, parent); + ecs_add_id(world, ai, EcsDisabled); + } + ecs_ftime_t t = timeout[i].inactive_time; + timeout[i].inactive_time += it->delta_system_time; + if (t < timeout[i].expire_time) { + /* Alert instance no longer matches rule, but is still + * within the timeout period. Keep it alive. */ + continue; + } + } + + /* Alert instance no longer matches rule, remove it */ + flecs_alerts_remove_alert_from_src(world, e, parent); + ecs_map_remove(&alert->instances, e); + ecs_delete(world, ai); + } + + ecs_vars_fini(&vars); +} + +ecs_entity_t ecs_alert_init( + ecs_world_t *world, + const ecs_alert_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!desc->filter.entity || desc->entity == desc->filter.entity, + ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world, 0); + } + + ecs_filter_desc_t private_desc = desc->filter; + private_desc.entity = result; + + ecs_rule_t *rule = ecs_rule_init(world, &private_desc); + if (!rule) { + ecs_err("failed to create alert filter"); + return 0; + } + + const ecs_filter_t *filter = ecs_rule_get_filter(rule); + if (!(filter->flags & EcsFilterMatchThis)) { + ecs_err("alert filter must have at least one '$this' term"); + ecs_rule_fini(rule); + return 0; + } + + /* Initialize Alert component which identifiers entity as alert */ + EcsAlert *alert = ecs_get_mut(world, result, EcsAlert); + ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); + alert->message = ecs_os_strdup(desc->message); + alert->retain_period = desc->retain_period; + + /* Initialize severity filters */ + int32_t i; + for (i = 0; i < 4; i ++) { + if (desc->severity_filters[i].with) { + if (!desc->severity_filters[i].severity) { + ecs_err("severity filter must have severity"); + goto error; + } + ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, + &alert->severity_filters, ecs_alert_severity_filter_t); + *sf = desc->severity_filters[i]; + if (sf->var) { + sf->_var_index = ecs_rule_find_var(rule, sf->var); + if (sf->_var_index == -1) { + ecs_err("unresolved variable '%s' in alert severity filter", + sf->var); + goto error; + } + } + } + } + + /* Fetch data for member monitoring */ + if (desc->member) { + alert->member = desc->member; + if (!desc->id) { + alert->id = ecs_get_parent(world, desc->member); + if (!alert->id) { + ecs_err("ecs_alert_desc_t::member is not a member"); + goto error; + } + ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL); + } else { + alert->id = desc->id; + } + + ecs_id_record_t *idr = flecs_id_record_ensure(world, alert->id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + if (!idr->type_info) { + ecs_err("ecs_alert_desc_t::id must be a component"); + goto error; + } + + ecs_entity_t type = idr->type_info->component; + if (type != ecs_get_parent(world, desc->member)) { + char *type_name = ecs_get_fullpath(world, type); + ecs_err("member '%s' is not a member of '%s'", + ecs_get_name(world, desc->member), type_name); + ecs_os_free(type_name); + goto error; + } + + const EcsMember *member = ecs_get(world, alert->member, EcsMember); + if (!member) { + ecs_err("ecs_alert_desc_t::member is not a member"); + goto error; + } + if (!member->type) { + ecs_err("ecs_alert_desc_t::member must have a type"); + goto error; + } + + const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive); + if (!pr) { + ecs_err("ecs_alert_desc_t::member must be of a primitive type"); + goto error; + } + + if (!ecs_has(world, desc->member, EcsMemberRanges)) { + ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); + goto error; + } + + int32_t var_id = 0; + if (desc->var) { + var_id = ecs_rule_find_var(rule, desc->var); + if (var_id == -1) { + ecs_err("unresolved variable '%s' in alert member", desc->var); + goto error; + } + } + + alert->offset = member->offset; + alert->size = idr->type_info->size; + alert->kind = pr->kind; + alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); + alert->var_id = var_id; + } + + ecs_modified(world, result, EcsAlert); + + /* Register alert as metric */ + ecs_add(world, result, EcsMetric); + ecs_add_pair(world, result, EcsMetric, EcsCounter); + + /* Add severity to alert */ + ecs_entity_t severity = desc->severity; + if (!severity) { + severity = EcsAlertError; + } + + ecs_add_pair(world, result, ecs_id(EcsAlert), severity); + + if (desc->doc_name) { +#ifdef FLECS_DOC + ecs_doc_set_name(world, result, desc->doc_name); +#else + ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon"); + goto error; +#endif + } + + if (desc->brief) { +#ifdef FLECS_DOC + ecs_doc_set_brief(world, result, desc->brief); +#else + ecs_err("cannot set brief for alert, requires FLECS_DOC addon"); + goto error; +#endif + } + + return result; +error: + if (result) { + ecs_delete(world, result); + } + return 0; +} + +int32_t ecs_get_alert_count( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!alert || ecs_has(world, alert, EcsAlert), + ECS_INVALID_PARAMETER, NULL); + + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { + return 0; + } + + if (alert) { + return ecs_map_get(&active->alerts, alert) != NULL; + } + + return ecs_map_count(&active->alerts); +error: + return 0; +} + +ecs_entity_t ecs_get_alert( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { + return 0; + } + + ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); + if (ptr) { + return ptr[0]; + } + +error: + return 0; +} + +void FlecsAlertsImport(ecs_world_t *world) { + ECS_MODULE_DEFINE(world, FlecsAlerts); + + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); + ECS_IMPORT(world, FlecsMetrics); +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); +#endif + + ecs_set_name_prefix(world, "Ecs"); + ECS_COMPONENT_DEFINE(world, EcsAlert); + ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol); + ECS_COMPONENT_DEFINE(world, EcsAlertsActive); + + ecs_set_name_prefix(world, "EcsAlert"); + ECS_COMPONENT_DEFINE(world, EcsAlertInstance); + ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); + + ECS_TAG_DEFINE(world, EcsAlertInfo); + ECS_TAG_DEFINE(world, EcsAlertWarning); + ECS_TAG_DEFINE(world, EcsAlertError); + ECS_TAG_DEFINE(world, EcsAlertCritical); + + ecs_add_id(world, ecs_id(EcsAlert), EcsTag); + ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); + ecs_add_id(world, ecs_id(EcsAlertsActive), EcsPrivate); + + ecs_struct(world, { + .entity = ecs_id(EcsAlertInstance), + .members = { + { .name = "message", .type = ecs_id(ecs_string_t) } + } + }); + + ecs_set_hooks(world, EcsAlert, { + .ctor = ecs_ctor(EcsAlert), + .dtor = ecs_dtor(EcsAlert), + .move = ecs_move(EcsAlert) + }); + + ecs_set_hooks(world, EcsAlertsActive, { + .ctor = ecs_ctor(EcsAlertsActive), + .dtor = ecs_dtor(EcsAlertsActive), + .move = ecs_move(EcsAlertsActive) + }); + + ecs_set_hooks(world, EcsAlertInstance, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsAlertInstance), + .move = ecs_move(EcsAlertInstance), + .copy = ecs_copy(EcsAlertInstance) + }); + + ecs_struct(world, { + .entity = ecs_id(EcsAlertsActive), + .members = { + { .name = "info_count", .type = ecs_id(ecs_i32_t) }, + { .name = "warning_count", .type = ecs_id(ecs_i32_t) }, + { .name = "error_count", .type = ecs_id(ecs_i32_t) } + } + }); + + ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); + ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, + flecs.metrics.Source, flecs.metrics.Value, ?EcsAlertTimeout, ?Disabled); + + ecs_system(world, { + .entity = ecs_id(MonitorAlerts), + .no_readonly = true, + .interval = 0.5 + }); + + ecs_system(world, { + .entity = ecs_id(MonitorAlertInstances), + .interval = 0.5 + }); +} + +#endif + +/** + * @file addons/app.c + * @brief App addon. + */ + + +#ifdef FLECS_APP + +static +int flecs_default_run_action( + ecs_world_t *world, + ecs_app_desc_t *desc) +{ + if (desc->init) { + desc->init(world); + } + + int result = 0; + if (desc->frames) { + int32_t i; + for (i = 0; i < desc->frames; i ++) { + if ((result = ecs_app_run_frame(world, desc)) != 0) { + break; + } + } + } else { + while ((result = ecs_app_run_frame(world, desc)) == 0) { } + } + + /* Ensure quit flag is set on world, which can be used to determine if + * world needs to be cleaned up. */ +#ifndef __EMSCRIPTEN__ + ecs_quit(world); +#endif + + if (result == 1) { + return 0; /* Normal exit */ + } else { + return result; /* Error code */ + } +} + +static +int flecs_default_frame_action( + ecs_world_t *world, + const ecs_app_desc_t *desc) +{ + return !ecs_progress(world, desc->delta_time); +} + +static ecs_app_run_action_t run_action = flecs_default_run_action; +static ecs_app_frame_action_t frame_action = flecs_default_frame_action; +static ecs_app_desc_t ecs_app_desc; + +/* Serve REST API from wasm image when running in emscripten */ +#ifdef ECS_TARGET_EM +#include + +ecs_http_server_t *flecs_wasm_rest_server; + +EMSCRIPTEN_KEEPALIVE +char* flecs_explorer_request(const char *method, char *request) { + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); + if (reply.code == 200) { + return ecs_strbuf_get(&reply.body); + } else { + char *body = ecs_strbuf_get(&reply.body); + if (body) { + return body; + } else { + return ecs_asprintf( + "{\"error\": \"bad request (code %d)\"}", reply.code); + } + } +} +#endif + +int ecs_app_run( + ecs_world_t *world, + ecs_app_desc_t *desc) +{ + ecs_app_desc = *desc; + + /* Don't set FPS & threads if custom run action is set, as the platform on + * which the app is running may not support it. */ + if (run_action == flecs_default_run_action) { + if (ECS_NEQZERO(ecs_app_desc.target_fps)) { + ecs_set_target_fps(world, ecs_app_desc.target_fps); + } + if (ecs_app_desc.threads) { + ecs_set_threads(world, ecs_app_desc.threads); + } + } + + /* REST server enables connecting to app with explorer */ + if (desc->enable_rest) { +#ifdef FLECS_REST +#ifdef ECS_TARGET_EM + flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); +#else + ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); +#endif +#else + ecs_warn("cannot enable remote API, REST addon not available"); +#endif + } + + /* Monitoring periodically collects statistics */ + if (desc->enable_monitor) { +#ifdef FLECS_MONITOR + ECS_IMPORT(world, FlecsMonitor); +#else + ecs_warn("cannot enable monitoring, MONITOR addon not available"); +#endif + } + + return run_action(world, &ecs_app_desc); +} + +int ecs_app_run_frame( + ecs_world_t *world, + const ecs_app_desc_t *desc) +{ + return frame_action(world, desc); +} + +int ecs_app_set_run_action( + ecs_app_run_action_t callback) +{ + if (run_action != flecs_default_run_action && run_action != callback) { + ecs_err("run action already set"); + return -1; + } + + run_action = callback; + + return 0; +} + +int ecs_app_set_frame_action( + ecs_app_frame_action_t callback) +{ + if (frame_action != flecs_default_frame_action && frame_action != callback) { + ecs_err("frame action already set"); + return -1; + } + + frame_action = callback; + + return 0; +} + +#endif + +/** + * @file addons/coredoc.c + * @brief Core doc addon. + */ + + +#ifdef FLECS_COREDOC + +#define URL_ROOT "https://www.flecs.dev/flecs/md_docs_2Relationships.html" + +void FlecsCoreDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsCoreDoc); + + ECS_IMPORT(world, FlecsMeta); + ECS_IMPORT(world, FlecsDoc); + + ecs_set_name_prefix(world, "Ecs"); + + /* Initialize reflection data for core components */ + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsComponent), + .members = { + {.name = "size", .type = ecs_id(ecs_i32_t)}, + {.name = "alignment", .type = ecs_id(ecs_i32_t)} + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsDocDescription), + .members = { + {.name = "value", .type = ecs_id(ecs_string_t)} + } + }); + + /* Initialize documentation data for core components */ + ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); + ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); + ecs_doc_set_brief(world, EcsFlecsCore, "Module with builtin components"); + ecs_doc_set_brief(world, EcsFlecsInternals, "Module with internal entities"); + + ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); + + ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to components"); + ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); + ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); + ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); + ecs_doc_set_brief(world, EcsPrivate, "Tag that is added to private components"); + ecs_doc_set_brief(world, EcsFlag, "Internal tag for tracking ids with special id flags"); + ecs_doc_set_brief(world, ecs_id(EcsIterable), "Internal component to make (query) entities iterable"); + ecs_doc_set_brief(world, ecs_id(EcsPoly), "Internal component that stores pointer to poly objects"); + + ecs_doc_set_brief(world, ecs_id(EcsTarget), "Internal component that stores information for flattened trees"); + ecs_doc_set_brief(world, EcsFlatten, "Tag that when added to assembly automatically flattens tree"); + + ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); + ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to store entity name"); + ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to store entity symbol"); + ecs_doc_set_brief(world, EcsAlias, "Tag used with EcsIdentifier to store entity alias"); + + ecs_doc_set_brief(world, EcsQuery, "Tag added to query entities"); + ecs_doc_set_brief(world, EcsObserver, "Tag added to observer entities"); + + ecs_doc_set_brief(world, EcsTransitive, "Trait that enables transitive evaluation of relationships"); + ecs_doc_set_brief(world, EcsReflexive, "Trait that enables reflexive evaluation of relationships"); + ecs_doc_set_brief(world, EcsFinal, "Trait that indicates an entity cannot be inherited from"); + ecs_doc_set_brief(world, EcsDontInherit, "Trait that indicates it should not be inherited"); + ecs_doc_set_brief(world, EcsTag, "Trait that ensures a pair cannot contain a value"); + ecs_doc_set_brief(world, EcsAcyclic, "Trait that indicates a relationship is acyclic"); + ecs_doc_set_brief(world, EcsTraversable, "Trait that indicates a relationship is traversable"); + ecs_doc_set_brief(world, EcsExclusive, "Trait that ensures a relationship can only have one target"); + ecs_doc_set_brief(world, EcsSymmetric, "Trait that causes a relationship to be two-way"); + ecs_doc_set_brief(world, EcsWith, "Trait for adding additional components when a component is added"); + ecs_doc_set_brief(world, EcsAlwaysOverride, "Trait that indicates a component should always be overridden"); + ecs_doc_set_brief(world, EcsUnion, "Trait for creating a non-fragmenting relationship"); + ecs_doc_set_brief(world, EcsOneOf, "Trait that enforces target of relationship is a child of "); + ecs_doc_set_brief(world, EcsOnDelete, "Cleanup trait for specifying what happens when component is deleted"); + ecs_doc_set_brief(world, EcsOnDeleteTarget, "Cleanup trait for specifying what happens when pair target is deleted"); + ecs_doc_set_brief(world, EcsRemove, "Cleanup action used with OnDelete/OnDeleteTarget"); + ecs_doc_set_brief(world, EcsDelete, "Cleanup action used with OnDelete/OnDeleteTarget"); + ecs_doc_set_brief(world, EcsPanic, "Cleanup action used with OnDelete/OnDeleteTarget"); + ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); + ecs_doc_set_brief(world, EcsIsA, "Relationship used for expressing inheritance"); + ecs_doc_set_brief(world, EcsChildOf, "Relationship used for expressing hierarchies"); + ecs_doc_set_brief(world, EcsDependsOn, "Relationship used for expressing dependencies"); + ecs_doc_set_brief(world, EcsSlotOf, "Relationship used for expressing prefab slots"); + ecs_doc_set_brief(world, EcsOnAdd, "Event emitted when component is added"); + ecs_doc_set_brief(world, EcsOnRemove, "Event emitted when component is removed"); + ecs_doc_set_brief(world, EcsOnSet, "Event emitted when component is set"); + ecs_doc_set_brief(world, EcsUnSet, "Event emitted when component is unset"); + ecs_doc_set_brief(world, EcsMonitor, "Marker used to create monitor observers"); + ecs_doc_set_brief(world, EcsOnTableFill, "Event emitted when table becomes non-empty"); + ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); + ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); + ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); + + ecs_doc_set_brief(world, EcsThis, "Query marker to express $this variable"); + ecs_doc_set_brief(world, EcsWildcard, "Query marker to express match all wildcard"); + ecs_doc_set_brief(world, EcsAny, "Query marker to express match at least one wildcard"); + + ecs_doc_set_brief(world, EcsPredEq, "Query marker to express == operator"); + ecs_doc_set_brief(world, EcsPredMatch, "Query marker to express ~= operator"); + ecs_doc_set_brief(world, EcsPredLookup, "Query marker to express by-name lookup"); + ecs_doc_set_brief(world, EcsScopeOpen, "Query marker to express scope open"); + ecs_doc_set_brief(world, EcsScopeClose, "Query marker to express scope close"); + ecs_doc_set_brief(world, EcsEmpty, "Tag used to indicate a query has no results"); + + /* Initialize documentation for meta components */ + ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); + ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); + + ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); + ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); + ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); + ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); + ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); + ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); + ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); + ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); + ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); + + ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); + ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); + ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); + ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); + ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); + ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); + ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); + ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); + + /* Initialize documentation for doc components */ + ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); + ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); + + ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); + ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); + ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); + ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); +} + +#endif + +/** + * @file addons/doc.c + * @brief Doc addon. + */ + + +#ifdef FLECS_DOC + +static ECS_COPY(EcsDocDescription, dst, src, { + ecs_os_strset((char**)&dst->value, src->value); + +}) + +static ECS_MOVE(EcsDocDescription, dst, src, { + ecs_os_free((char*)dst->value); + dst->value = src->value; + src->value = NULL; +}) + +static ECS_DTOR(EcsDocDescription, ptr, { + ecs_os_free((char*)ptr->value); +}) + +static +void flecs_doc_set( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t kind, + const char *value) +{ + if (value) { + ecs_set_pair(world, entity, EcsDocDescription, kind, { + /* Safe, value gets copied by copy hook */ + .value = ECS_CONST_CAST(char*, value) + }); + } else { + ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind); + } +} + +void ecs_doc_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + flecs_doc_set(world, entity, EcsName, name); +} + +void ecs_doc_set_brief( + ecs_world_t *world, + ecs_entity_t entity, + const char *brief) +{ + flecs_doc_set(world, entity, EcsDocBrief, brief); +} + +void ecs_doc_set_detail( + ecs_world_t *world, + ecs_entity_t entity, + const char *detail) +{ + flecs_doc_set(world, entity, EcsDocDetail, detail); +} + +void ecs_doc_set_link( + ecs_world_t *world, + ecs_entity_t entity, + const char *link) +{ + flecs_doc_set(world, entity, EcsDocLink, link); +} + +void ecs_doc_set_color( + ecs_world_t *world, + ecs_entity_t entity, + const char *color) +{ + flecs_doc_set(world, entity, EcsDocColor, color); +} + +const char* ecs_doc_get_name( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsName); + if (ptr) { + return ptr->value; + } else { + return ecs_get_name(world, entity); + } +} + +const char* ecs_doc_get_brief( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocBrief); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +const char* ecs_doc_get_detail( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocDetail); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +const char* ecs_doc_get_link( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocLink); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +const char* ecs_doc_get_color( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocColor); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +void FlecsDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsDoc); + + ecs_set_name_prefix(world, "EcsDoc"); + + flecs_bootstrap_component(world, EcsDocDescription); + flecs_bootstrap_tag(world, EcsDocBrief); + flecs_bootstrap_tag(world, EcsDocDetail); + flecs_bootstrap_tag(world, EcsDocLink); + flecs_bootstrap_tag(world, EcsDocColor); + + ecs_set_hooks(world, EcsDocDescription, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsDocDescription), + .copy = ecs_copy(EcsDocDescription), + .dtor = ecs_dtor(EcsDocDescription) + }); + + ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); + ecs_add_id(world, ecs_id(EcsDocDescription), EcsPrivate); + + ecs_doc_set_brief(world, EcsDocBrief, "Brief description"); + ecs_doc_set_brief(world, EcsDocDetail, "Detailed description"); + ecs_doc_set_brief(world, EcsDocLink, "Link to additional documentation"); + ecs_doc_set_brief(world, EcsDocColor, "Color hint for entity"); +} + +#endif + +/** + * @file addons/flecs_cpp.c + * @brief Utilities for C++ addon. + */ + +#include + +/* Utilities for C++ API */ + +#ifdef FLECS_CPP + +/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to + * a uniform identifier */ + +#define ECS_CONST_PREFIX "const " +#define ECS_STRUCT_PREFIX "struct " +#define ECS_CLASS_PREFIX "class " +#define ECS_ENUM_PREFIX "enum " + +#define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) +#define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) +#define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) +#define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) + +static +ecs_size_t ecs_cpp_strip_prefix( + char *typeName, + ecs_size_t len, + const char *prefix, + ecs_size_t prefix_len) +{ + if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { + ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); + typeName[len - prefix_len] = '\0'; + len -= prefix_len; + } + return len; +} + +static +void ecs_cpp_trim_type_name( + char *typeName) +{ + ecs_size_t len = ecs_os_strlen(typeName); + + len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); + + while (typeName[len - 1] == ' ' || + typeName[len - 1] == '&' || + typeName[len - 1] == '*') + { + len --; + typeName[len] = '\0'; + } + + /* Remove const at end of string */ + if (len > ECS_CONST_LEN) { + if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { + typeName[len - ECS_CONST_LEN] = '\0'; + } + len -= ECS_CONST_LEN; + } + + /* Check if there are any remaining "struct " strings, which can happen + * if this is a template type on msvc. */ + if (len > ECS_STRUCT_LEN) { + char *ptr = typeName; + while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { + /* Make sure we're not matched with part of a longer identifier + * that contains 'struct' */ + if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { + ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, + ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); + len -= ECS_STRUCT_LEN; + } + } + } +} + +char* ecs_cpp_get_type_name( + char *type_name, + const char *func_name, + size_t len, + size_t front_len) +{ + memcpy(type_name, func_name + front_len, len); + type_name[len] = '\0'; + ecs_cpp_trim_type_name(type_name); + return type_name; +} + +char* ecs_cpp_get_symbol_name( + char *symbol_name, + const char *type_name, + size_t len) +{ + // Symbol is same as name, but with '::' replaced with '.' + ecs_os_strcpy(symbol_name, type_name); + + char *ptr; + size_t i; + for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { + if (*ptr == ':') { + symbol_name[i] = '.'; + ptr ++; + } else { + symbol_name[i] = *ptr; + } + } + + symbol_name[i] = '\0'; + + return symbol_name; +} + +static +const char* flecs_cpp_func_rchr( + const char *func_name, + ecs_size_t func_name_len, + ecs_size_t func_back_len, + char ch) +{ + const char *r = strrchr(func_name, ch); + if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { + return NULL; + } + return r; +} + +static +const char* flecs_cpp_func_max( + const char *a, + const char *b) +{ + if (a > b) return a; + return b; +} + +char* ecs_cpp_get_constant_name( + char *constant_name, + const char *func_name, + size_t func_name_len, + size_t func_back_len) +{ + ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); + ecs_size_t fb_len = flecs_uto(ecs_size_t, func_back_len); + const char *start = flecs_cpp_func_rchr(func_name, f_len, fb_len, ' '); + start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( + func_name, f_len, fb_len, ')')); + start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( + func_name, f_len, fb_len, ':')); + start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( + func_name, f_len, fb_len, ',')); + ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); + start ++; + + ecs_size_t len = flecs_uto(ecs_size_t, + (f_len - (start - func_name) - fb_len)); + ecs_os_memcpy_n(constant_name, start, char, len); + constant_name[len] = '\0'; + return constant_name; +} + +// Names returned from the name_helper class do not start with :: +// but are relative to the root. If the namespace of the type +// overlaps with the namespace of the current module, strip it from +// the implicit identifier. +// This allows for registration of component types that are not in the +// module namespace to still be registered under the module scope. +const char* ecs_cpp_trim_module( + ecs_world_t *world, + const char *type_name) +{ + ecs_entity_t scope = ecs_get_scope(world); + if (!scope) { + return type_name; + } + + char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); + if (path) { + ecs_size_t len = ecs_os_strlen(path); + if (!ecs_os_strncmp(path, type_name, len)) { + // Type is a child of current parent, trim name of parent + type_name += len; + ecs_assert(type_name[0], ECS_INVALID_PARAMETER, NULL); + ecs_assert(type_name[0] == ':', ECS_INVALID_PARAMETER, NULL); + ecs_assert(type_name[1] == ':', ECS_INVALID_PARAMETER, NULL); + type_name += 2; + } else { + // Type is not a child of current parent, trim entire path + char *ptr = strrchr(type_name, ':'); + if (ptr) { + type_name = ptr + 1; + } + + } + } + ecs_os_free(path); + + return type_name; +} + +// Validate registered component +void ecs_cpp_component_validate( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + size_t size, + size_t alignment, + bool implicit_name) +{ + /* If entity has a name check if it matches */ + if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { + if (!implicit_name && id >= EcsFirstUserComponentId) { +#ifndef FLECS_NDEBUG + char *path = ecs_get_path_w_sep( + world, 0, id, "::", NULL); + if (ecs_os_strcmp(path, name)) { + ecs_abort(ECS_INCONSISTENT_NAME, + "component '%s' already registered with name '%s'", + name, path); + } + ecs_os_free(path); +#endif + } + + if (symbol) { + const char *existing_symbol = ecs_get_symbol(world, id); + if (existing_symbol) { + if (ecs_os_strcmp(symbol, existing_symbol)) { + ecs_abort(ECS_INCONSISTENT_NAME, + "component '%s' with symbol '%s' already registered with symbol '%s'", + name, symbol, existing_symbol); + } + } + } + } else { + /* Ensure that the entity id valid */ + if (!ecs_is_alive(world, id)) { + ecs_ensure(world, id); + } + + /* Register name with entity, so that when the entity is created the + * correct id will be resolved from the name. Only do this when the + * entity is empty. */ + ecs_add_path_w_sep(world, id, 0, name, "::", "::"); + } + + /* If a component was already registered with this id but with a + * different size, the ecs_component_init function will fail. */ + + /* We need to explicitly call ecs_component_init 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 ent = ecs_component_init(world, &(ecs_component_desc_t){ + .entity = id, + .type.size = flecs_uto(int32_t, size), + .type.alignment = flecs_uto(int32_t, alignment) + }); + (void)ent; + ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); +} + +ecs_entity_t ecs_cpp_component_register( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment, + bool implicit_name, + bool *existing_out) +{ + (void)size; + (void)alignment; + + /* If the component is not yet registered, ensure no other component + * or entity has been registered with this name. Ensure component is + * looked up from root. */ + bool existing = false; + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + ecs_entity_t ent; + if (id) { + ent = id; + } else { + ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); + existing = ent != 0 && ecs_has(world, ent, EcsComponent); + } + ecs_set_scope(world, prev_scope); + + /* If entity exists, compare symbol name to ensure that the component + * we are trying to register under this name is the same */ + if (ent) { + const EcsComponent *component = ecs_get(world, ent, EcsComponent); + if (component != NULL) { + const char *sym = ecs_get_symbol(world, ent); + if (sym && ecs_os_strcmp(sym, symbol)) { + /* Application is trying to register a type with an entity that + * was already associated with another type. In most cases this + * is an error, with the exception of a scenario where the + * application is wrapping a C type with a C++ type. + * + * In this case the C++ type typically inherits from the C type, + * and adds convenience methods to the derived class without + * changing anything that would change the size or layout. + * + * To meet this condition, the new type must have the same size + * and alignment as the existing type, and the name of the type + * type must be equal to the registered name (not symbol). + * + * The latter ensures that it was the intent of the application + * to alias the type, vs. accidentally registering an unrelated + * type with the same size/alignment. */ + char *type_path = ecs_get_fullpath(world, ent); + if (ecs_os_strcmp(type_path, symbol) || + component->size != size || + component->alignment != alignment) + { + ecs_err( + "component with name '%s' is already registered for"\ + " type '%s' (trying to register for type '%s')", + name, sym, symbol); + ecs_abort(ECS_NAME_IN_USE, NULL); + } + ecs_os_free(type_path); + } else if (!sym) { + ecs_set_symbol(world, ent, symbol); + } + } + + /* If no entity is found, lookup symbol to check if the component was + * registered under a different name. */ + } else if (!implicit_name) { + ent = ecs_lookup_symbol(world, symbol, false, false); + ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); + } + + if (existing_out) { + *existing_out = existing; + } + + return ent; +} + +ecs_entity_t ecs_cpp_component_register_explicit( + ecs_world_t *world, + ecs_entity_t s_id, + ecs_entity_t id, + const char *name, + const char *type_name, + const char *symbol, + size_t size, + size_t alignment, + bool is_component, + bool *existing_out) +{ + char *existing_name = NULL; + if (existing_out) *existing_out = false; + + // If an explicit id is provided, it is possible that the symbol and + // name differ from the actual type, as the application may alias + // one type to another. + if (!id) { + if (!name) { + // If no name was provided first check if a type with the provided + // symbol was already registered. + id = ecs_lookup_symbol(world, symbol, false, false); + if (id) { + existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); + name = existing_name; + if (existing_out) *existing_out = true; + } else { + // If type is not yet known, derive from type name + name = ecs_cpp_trim_module(world, type_name); + } + } + } else { + // If an explicit id is provided but it has no name, inherit + // the name from the type. + if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { + name = ecs_cpp_trim_module(world, type_name); + } + } + + ecs_entity_t entity; + if (is_component || size != 0) { + entity = ecs_entity(world, { + .id = s_id, + .name = name, + .sep = "::", + .root_sep = "::", + .symbol = symbol, + .use_low_id = true + }); + ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); + + entity = ecs_component_init(world, &(ecs_component_desc_t){ + .entity = entity, + .type.size = flecs_uto(int32_t, size), + .type.alignment = flecs_uto(int32_t, alignment) + }); + ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); + } else { + entity = ecs_entity(world, { + .id = s_id, + .name = name, + .sep = "::", + .root_sep = "::", + .symbol = symbol, + .use_low_id = true + }); + } + + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(existing_name); + + return entity; +} + +void ecs_cpp_enum_init( + ecs_world_t *world, + ecs_entity_t id) +{ + (void)world; + (void)id; +#ifdef FLECS_META + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); + ecs_set(world, id, EcsEnum, {0}); + flecs_resume_readonly(world, &readonly_state); +#endif +} + +ecs_entity_t ecs_cpp_enum_constant_register( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t id, + const char *name, + int value) +{ + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); + + const char *parent_name = ecs_get_name(world, parent); + ecs_size_t parent_name_len = ecs_os_strlen(parent_name); + if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { + name += parent_name_len; + if (name[0] == '_') { + name ++; + } + } + + ecs_entity_t prev = ecs_set_scope(world, parent); + id = ecs_entity(world, { + .id = id, + .name = name + }); + ecs_assert(id != 0, ECS_INVALID_OPERATION, name); + ecs_set_scope(world, prev); + + #ifdef FLECS_DEBUG + const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); + ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, + "enum component must have 32bit size"); + #endif + +#ifdef FLECS_META + ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)), + sizeof(ecs_i32_t), &value); +#endif + + flecs_resume_readonly(world, &readonly_state); + + ecs_trace("#[green]constant#[reset] %s.%s created with value %d", + ecs_get_name(world, parent), name, value); + + return id; +} + +static int32_t flecs_reset_count = 0; + +int32_t ecs_cpp_reset_count_get(void) { + return flecs_reset_count; +} + +int32_t ecs_cpp_reset_count_inc(void) { + return ++flecs_reset_count; +} + +#ifdef FLECS_META +const ecs_member_t* ecs_cpp_last_member( + const ecs_world_t *world, + ecs_entity_t type) +{ + const EcsStruct *st = ecs_get(world, type, EcsStruct); + if (!st) { + char *type_str = ecs_get_fullpath(world, type); + ecs_err("entity '%s' is not a struct", type_str); + ecs_os_free(type_str); + return 0; + } + + ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, + ecs_vec_count(&st->members) - 1); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + return m; +} +#endif + +#endif + +/** + * @file addons/http.c + * @brief HTTP addon. + * + * This is a heavily modified version of the EmbeddableWebServer (see copyright + * below). This version has been stripped from everything not strictly necessary + * for receiving/replying to simple HTTP requests, and has been modified to use + * the Flecs OS API. + * + * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and + * CONTRIBUTORS (see below) - All rights reserved. + * + * CONTRIBUTORS: + * Martin Pulec - bug fixes, warning fixes, IPv6 support + * Daniel Barry - bug fix (ifa_addr != NULL) + * + * Released under the BSD 2-clause license: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. THIS SOFTWARE IS + * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifdef FLECS_HTTP + +#ifdef ECS_TARGET_MSVC +#pragma comment(lib, "Ws2_32.lib") +#endif + +#if defined(ECS_TARGET_WINDOWS) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +typedef SOCKET ecs_http_socket_t; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __FreeBSD__ +#include +#endif +typedef int ecs_http_socket_t; + +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL (0) +#endif + +#endif + +/* Max length of request method */ +#define ECS_HTTP_METHOD_LEN_MAX (8) + +/* Timeout (s) before connection purge */ +#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) + +/* Number of dequeues before purging */ +#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) + +/* Number of retries receiving request */ +#define ECS_HTTP_REQUEST_RECV_RETRY (10) + +/* Minimum interval between dequeueing requests (ms) */ +#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) + +/* Minimum interval between printing statistics (ms) */ +#define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) + +/* Receive buffer size */ +#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) + +/* Max length of request (path + query + headers + body) */ +#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) + +/* Total number of outstanding send requests */ +#define ECS_HTTP_SEND_QUEUE_MAX (256) + +/* Global statistics */ +int64_t ecs_http_request_received_count = 0; +int64_t ecs_http_request_invalid_count = 0; +int64_t ecs_http_request_handled_ok_count = 0; +int64_t ecs_http_request_handled_error_count = 0; +int64_t ecs_http_request_not_handled_count = 0; +int64_t ecs_http_request_preflight_count = 0; +int64_t ecs_http_send_ok_count = 0; +int64_t ecs_http_send_error_count = 0; +int64_t ecs_http_busy_count = 0; + +/* Send request queue */ +typedef struct ecs_http_send_request_t { + ecs_http_socket_t sock; + char *headers; + int32_t header_length; + char *content; + int32_t content_length; +} ecs_http_send_request_t; + +typedef struct ecs_http_send_queue_t { + ecs_http_send_request_t requests[ECS_HTTP_SEND_QUEUE_MAX]; + int32_t head; + int32_t tail; + ecs_os_thread_t thread; + int32_t wait_ms; +} ecs_http_send_queue_t; + +typedef struct ecs_http_request_key_t { + const char *array; + ecs_size_t count; +} ecs_http_request_key_t; + +typedef struct ecs_http_request_entry_t { + char *content; + int32_t content_length; + int code; + ecs_ftime_t time; +} ecs_http_request_entry_t; + +/* HTTP server struct */ +struct ecs_http_server_t { + bool should_run; + bool running; + + ecs_http_socket_t sock; + ecs_os_mutex_t lock; + ecs_os_thread_t thread; + + ecs_http_reply_action_t callback; + void *ctx; + + ecs_ftime_t cache_timeout; + ecs_ftime_t cache_purge_timeout; + + ecs_sparse_t connections; /* sparse */ + ecs_sparse_t requests; /* sparse */ + + bool initialized; + + uint16_t port; + const char *ipaddr; + + double dequeue_timeout; /* used to not lock request queue too often */ + double stats_timeout; /* used for periodic reporting of statistics */ + + double request_time; /* time spent on requests in last stats interval */ + double request_time_total; /* total time spent on requests */ + int32_t requests_processed; /* requests processed in last stats interval */ + int32_t requests_processed_total; /* total requests processed */ + int32_t dequeue_count; /* number of dequeues in last stats interval */ + ecs_http_send_queue_t send_queue; + + ecs_hashmap_t request_cache; +}; + +/** Fragment state, used by HTTP request parser */ +typedef enum { + HttpFragStateBegin, + HttpFragStateMethod, + HttpFragStatePath, + HttpFragStateVersion, + HttpFragStateHeaderStart, + HttpFragStateHeaderName, + HttpFragStateHeaderValueStart, + HttpFragStateHeaderValue, + HttpFragStateCR, + HttpFragStateCRLF, + HttpFragStateCRLFCR, + HttpFragStateBody, + HttpFragStateDone +} HttpFragState; + +/** A fragment is a partially received HTTP request */ +typedef struct { + HttpFragState state; + ecs_strbuf_t buf; + ecs_http_method_t method; + int32_t body_offset; + int32_t query_offset; + int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_count; + int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; + int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; + int32_t param_count; + int32_t content_length; + char *header_buf_ptr; + char header_buf[32]; + bool parse_content_length; + bool invalid; +} ecs_http_fragment_t; + +/** Extend public connection type with fragment data */ +typedef struct { + ecs_http_connection_t pub; + ecs_http_socket_t sock; + + /* Connection is purged after both timeout expires and connection has + * exceeded retry count. This ensures that a connection does not immediately + * timeout when a frame takes longer than usual */ + double dequeue_timeout; + int32_t dequeue_retries; +} ecs_http_connection_impl_t; + +typedef struct { + ecs_http_request_t pub; + uint64_t conn_id; /* for sanity check */ + char *res; + int32_t req_len; +} ecs_http_request_impl_t; + +static +ecs_size_t http_send( + ecs_http_socket_t sock, + const void *buf, + ecs_size_t size, + int flags) +{ + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); +#ifdef ECS_TARGET_POSIX + ssize_t send_bytes = send(sock, buf, flecs_itosize(size), + flags | MSG_NOSIGNAL); + return flecs_itoi32(send_bytes); +#else + int send_bytes = send(sock, buf, size, flags); + return flecs_itoi32(send_bytes); +#endif +} + +static +ecs_size_t http_recv( + ecs_http_socket_t sock, + void *buf, + ecs_size_t size, + int flags) +{ + ecs_size_t ret; +#ifdef ECS_TARGET_POSIX + ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); + ret = flecs_itoi32(recv_bytes); +#else + int recv_bytes = recv(sock, buf, size, flags); + ret = flecs_itoi32(recv_bytes); +#endif + if (ret == -1) { + ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); + } else if (ret == 0) { + ecs_dbg("recv: received 0 bytes (sock = %d)", sock); + } + + return ret; +} + +static +void http_sock_set_timeout( + ecs_http_socket_t sock, + int32_t timeout_ms) +{ + int r; +#ifdef ECS_TARGET_POSIX + struct timeval tv; + tv.tv_sec = timeout_ms * 1000; + tv.tv_usec = 0; + r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); +#else + DWORD t = (DWORD)timeout_ms; + r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&t, sizeof t); +#endif + if (r) { + ecs_warn("http: failed to set socket timeout: %s", + ecs_os_strerror(errno)); + } +} + +static +void http_sock_keep_alive( + ecs_http_socket_t sock) +{ + int v = 1; + if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&v, sizeof v)) { + ecs_warn("http: failed to set socket KEEPALIVE: %s", + ecs_os_strerror(errno)); + } +} + +static +void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { + (void)sock; + (void)enable; +#ifdef ECS_TARGET_POSIX + int flags; + flags = fcntl(sock,F_GETFL,0); + if (flags == -1) { + ecs_warn("http: failed to set socket NONBLOCK: %s", + ecs_os_strerror(errno)); + return; + } + if (enable) { + flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK); + } else { + flags = fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); + } + if (flags == -1) { + ecs_warn("http: failed to set socket NONBLOCK: %s", + ecs_os_strerror(errno)); + return; + } +#endif +} + +static +int http_getnameinfo( + const struct sockaddr* addr, + ecs_size_t addr_len, + char *host, + ecs_size_t host_len, + char *port, + ecs_size_t port_len, + int flags) +{ + ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); +#if defined(ECS_TARGET_WINDOWS) + return getnameinfo(addr, addr_len, host, + flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), + flags); +#else + return getnameinfo(addr, flecs_ito(uint32_t, addr_len), host, + flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), + flags); +#endif +} + +static +int http_bind( + ecs_http_socket_t sock, + const struct sockaddr* addr, + ecs_size_t addr_len) +{ + ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); +#if defined(ECS_TARGET_WINDOWS) + return bind(sock, addr, addr_len); +#else + return bind(sock, addr, flecs_ito(uint32_t, addr_len)); +#endif +} + +static +bool http_socket_is_valid( + ecs_http_socket_t sock) +{ +#if defined(ECS_TARGET_WINDOWS) + return sock != INVALID_SOCKET; +#else + return sock >= 0; +#endif +} + +#if defined(ECS_TARGET_WINDOWS) +#define HTTP_SOCKET_INVALID INVALID_SOCKET +#else +#define HTTP_SOCKET_INVALID (-1) +#endif + +static +void http_close( + ecs_http_socket_t *sock) +{ + ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); + +#if defined(ECS_TARGET_WINDOWS) + closesocket(*sock); +#else + ecs_dbg_2("http: closing socket %u", *sock); + shutdown(*sock, SHUT_RDWR); + close(*sock); +#endif + *sock = HTTP_SOCKET_INVALID; +} + +static +ecs_http_socket_t http_accept( + ecs_http_socket_t sock, + struct sockaddr* addr, + ecs_size_t *addr_len) +{ + socklen_t len = (socklen_t)addr_len[0]; + ecs_http_socket_t result = accept(sock, addr, &len); + addr_len[0] = (ecs_size_t)len; + return result; +} + +static +void http_reply_fini(ecs_http_reply_t* reply) { + ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(reply->body.content); +} + +static +void http_request_fini(ecs_http_request_impl_t *req) { + ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(req->res); + flecs_sparse_remove_t(&req->pub.conn->server->requests, + ecs_http_request_impl_t, req->pub.id); +} + +static +void http_connection_free(ecs_http_connection_impl_t *conn) { + ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); + uint64_t conn_id = conn->pub.id; + + if (http_socket_is_valid(conn->sock)) { + http_close(&conn->sock); + } + + flecs_sparse_remove_t(&conn->pub.server->connections, + ecs_http_connection_impl_t, conn_id); +} + +// https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int +static +char http_hex_2_int(char a, char b){ + a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); + b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); + return (char)((a << 4) + b); +} + +static +void http_decode_url_str( + char *str) +{ + char ch, *ptr, *dst = str; + for (ptr = str; (ch = *ptr); ptr++) { + if (ch == '%') { + dst[0] = http_hex_2_int(ptr[1], ptr[2]); + dst ++; + ptr += 2; + } else { + dst[0] = ptr[0]; + dst ++; + } + } + dst[0] = '\0'; +} + +static +void http_parse_method( + ecs_http_fragment_t *frag) +{ + char *method = ecs_strbuf_get_small(&frag->buf); + if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; + else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; + else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; + else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; + else if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; + else { + frag->method = EcsHttpMethodUnsupported; + frag->invalid = true; + } + ecs_strbuf_reset(&frag->buf); +} + +static +bool http_header_writable( + ecs_http_fragment_t *frag) +{ + return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; +} + +static +void http_header_buf_reset( + ecs_http_fragment_t *frag) +{ + frag->header_buf[0] = '\0'; + frag->header_buf_ptr = frag->header_buf; +} + +static +void http_header_buf_append( + ecs_http_fragment_t *frag, + char ch) +{ + if ((frag->header_buf_ptr - frag->header_buf) < + ECS_SIZEOF(frag->header_buf)) + { + frag->header_buf_ptr[0] = ch; + frag->header_buf_ptr ++; + } else { + frag->header_buf_ptr[0] = '\0'; + } +} + +static +uint64_t http_request_key_hash(const void *ptr) { + const ecs_http_request_key_t *key = ptr; + const char *array = key->array; + int32_t count = key->count; + return flecs_hash(array, count * ECS_SIZEOF(char)); +} + +static +int http_request_key_compare(const void *ptr_1, const void *ptr_2) { + const ecs_http_request_key_t *type_1 = ptr_1; + const ecs_http_request_key_t *type_2 = ptr_2; + + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; + + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); + } + + return ecs_os_memcmp(type_1->array, type_2->array, count_1); +} + +static +ecs_http_request_entry_t* http_find_request_entry( + ecs_http_server_t *srv, + const char *array, + int32_t count) +{ + ecs_http_request_key_t key; + key.array = array; + key.count = count; + + ecs_time_t t = {0, 0}; + ecs_http_request_entry_t *entry = flecs_hashmap_get( + &srv->request_cache, &key, ecs_http_request_entry_t); + + if (entry) { + ecs_ftime_t tf = (ecs_ftime_t)ecs_time_measure(&t); + if ((tf - entry->time) < srv->cache_timeout) { + return entry; + } + } + return NULL; +} + +static +void http_insert_request_entry( + ecs_http_server_t *srv, + ecs_http_request_impl_t *req, + ecs_http_reply_t *reply) +{ + int32_t content_length = ecs_strbuf_written(&reply->body); + if (!content_length) { + return; + } + + ecs_http_request_key_t key; + key.array = req->res; + key.count = req->req_len; + ecs_http_request_entry_t *entry = flecs_hashmap_get( + &srv->request_cache, &key, ecs_http_request_entry_t); + if (!entry) { + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + &srv->request_cache, &key, ecs_http_request_entry_t); + ecs_http_request_key_t *elem_key = elem.key; + elem_key->array = ecs_os_memdup_n(key.array, char, key.count); + entry = elem.value; + } else { + ecs_os_free(entry->content); + } + + ecs_time_t t = {0, 0}; + entry->time = (ecs_ftime_t)ecs_time_measure(&t); + entry->content_length = ecs_strbuf_written(&reply->body); + entry->content = ecs_strbuf_get(&reply->body); + entry->code = reply->code; + ecs_strbuf_appendstrn(&reply->body, + entry->content, entry->content_length); +} + +static +char* http_decode_request( + ecs_http_request_impl_t *req, + ecs_http_fragment_t *frag) +{ + ecs_os_zeromem(req); + + ecs_size_t req_len = frag->buf.length; + char *res = ecs_strbuf_get(&frag->buf); + if (!res) { + return NULL; + } + + req->pub.method = frag->method; + req->pub.path = res + 1; + http_decode_url_str(req->pub.path); + + if (frag->body_offset) { + req->pub.body = &res[frag->body_offset]; + } + int32_t i, count = frag->header_count; + for (i = 0; i < count; i ++) { + req->pub.headers[i].key = &res[frag->header_offsets[i]]; + req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; + } + count = frag->param_count; + for (i = 0; i < count; i ++) { + req->pub.params[i].key = &res[frag->param_offsets[i]]; + req->pub.params[i].value = &res[frag->param_value_offsets[i]]; + /* Safe, member is only const so that end-user can't change it */ + http_decode_url_str(ECS_CONST_CAST(char*, req->pub.params[i].value)); + } + + req->pub.header_count = frag->header_count; + req->pub.param_count = frag->param_count; + req->res = res; + req->req_len = frag->header_offsets[0]; + if (!req->req_len) { + req->req_len = req_len; + } + + return res; +} + +static +ecs_http_request_entry_t* http_enqueue_request( + ecs_http_connection_impl_t *conn, + uint64_t conn_id, + ecs_http_fragment_t *frag) +{ + ecs_http_server_t *srv = conn->pub.server; + + ecs_os_mutex_lock(srv->lock); + bool is_alive = conn->pub.id == conn_id; + + if (!is_alive || frag->invalid) { + /* Don't enqueue invalid requests or requests for purged connections */ + ecs_strbuf_reset(&frag->buf); + } else { + ecs_http_request_impl_t req; + char *res = http_decode_request(&req, frag); + if (res) { + req.pub.conn = (ecs_http_connection_t*)conn; + + /* Check cache for GET requests */ + if (frag->method == EcsHttpGet) { + ecs_http_request_entry_t *entry = + http_find_request_entry(srv, res, frag->header_offsets[0]); + if (entry) { + /* If an entry is found, don't enqueue a request. Instead + * return the cached response immediately. */ + ecs_os_free(res); + return entry; + } + } + + ecs_http_request_impl_t *req_ptr = flecs_sparse_add_t( + &srv->requests, ecs_http_request_impl_t); + *req_ptr = req; + req_ptr->pub.id = flecs_sparse_last_id(&srv->requests); + req_ptr->conn_id = conn->pub.id; + ecs_os_linc(&ecs_http_request_received_count); + } + } + + ecs_os_mutex_unlock(srv->lock); + return NULL; +} + +static +bool http_parse_request( + ecs_http_fragment_t *frag, + const char* req_frag, + ecs_size_t req_frag_len) +{ + int32_t i; + for (i = 0; i < req_frag_len; i++) { + char c = req_frag[i]; + switch (frag->state) { + case HttpFragStateBegin: + ecs_os_memset_t(frag, 0, ecs_http_fragment_t); + frag->state = HttpFragStateMethod; + frag->header_buf_ptr = frag->header_buf; + + /* fall through */ + case HttpFragStateMethod: + if (c == ' ') { + http_parse_method(frag); + ecs_strbuf_reset(&frag->buf); + frag->state = HttpFragStatePath; + frag->buf.content = NULL; + } else { + ecs_strbuf_appendch(&frag->buf, c); + } + break; + case HttpFragStatePath: + if (c == ' ') { + frag->state = HttpFragStateVersion; + ecs_strbuf_appendch(&frag->buf, '\0'); + } else { + if (c == '?' || c == '=' || c == '&') { + ecs_strbuf_appendch(&frag->buf, '\0'); + int32_t offset = ecs_strbuf_written(&frag->buf); + if (c == '?' || c == '&') { + frag->param_offsets[frag->param_count] = offset; + } else { + frag->param_value_offsets[frag->param_count] = offset; + frag->param_count ++; + } + } else { + ecs_strbuf_appendch(&frag->buf, c); + } + } + break; + case HttpFragStateVersion: + if (c == '\r') { + frag->state = HttpFragStateCR; + } /* version is not stored */ + break; + case HttpFragStateHeaderStart: + if (http_header_writable(frag)) { + frag->header_offsets[frag->header_count] = + ecs_strbuf_written(&frag->buf); + } + http_header_buf_reset(frag); + frag->state = HttpFragStateHeaderName; + + /* fall through */ + case HttpFragStateHeaderName: + if (c == ':') { + frag->state = HttpFragStateHeaderValueStart; + http_header_buf_append(frag, '\0'); + frag->parse_content_length = !ecs_os_strcmp( + frag->header_buf, "Content-Length"); + + if (http_header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, '\0'); + frag->header_value_offsets[frag->header_count] = + ecs_strbuf_written(&frag->buf); + } + } else if (c == '\r') { + frag->state = HttpFragStateCR; + } else { + http_header_buf_append(frag, c); + if (http_header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, c); + } + } + break; + case HttpFragStateHeaderValueStart: + http_header_buf_reset(frag); + frag->state = HttpFragStateHeaderValue; + if (c == ' ') { /* skip first space */ + break; + } + + /* fall through */ + case HttpFragStateHeaderValue: + if (c == '\r') { + if (frag->parse_content_length) { + http_header_buf_append(frag, '\0'); + int32_t len = atoi(frag->header_buf); + if (len < 0) { + frag->invalid = true; + } else { + frag->content_length = len; + } + frag->parse_content_length = false; + } + if (http_header_writable(frag)) { + int32_t cur = ecs_strbuf_written(&frag->buf); + if (frag->header_offsets[frag->header_count] < cur && + frag->header_value_offsets[frag->header_count] < cur) + { + ecs_strbuf_appendch(&frag->buf, '\0'); + frag->header_count ++; + } + } + frag->state = HttpFragStateCR; + } else { + if (frag->parse_content_length) { + http_header_buf_append(frag, c); + } + if (http_header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, c); + } + } + break; + case HttpFragStateCR: + if (c == '\n') { + frag->state = HttpFragStateCRLF; + } else { + frag->state = HttpFragStateHeaderStart; + } + break; + case HttpFragStateCRLF: + if (c == '\r') { + frag->state = HttpFragStateCRLFCR; + } else { + frag->state = HttpFragStateHeaderStart; + i--; + } + break; + case HttpFragStateCRLFCR: + if (c == '\n') { + if (frag->content_length != 0) { + frag->body_offset = ecs_strbuf_written(&frag->buf); + frag->state = HttpFragStateBody; + } else { + frag->state = HttpFragStateDone; + } + } else { + frag->state = HttpFragStateHeaderStart; + } + break; + case HttpFragStateBody: { + ecs_strbuf_appendch(&frag->buf, c); + if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == + frag->content_length) + { + frag->state = HttpFragStateDone; + } + } + break; + case HttpFragStateDone: + break; + } + } + + if (frag->state == HttpFragStateDone) { + return true; + } else { + return false; + } +} + +static +ecs_http_send_request_t* http_send_queue_post( + ecs_http_server_t *srv) +{ + /* This function should only be called while the server is locked. Before + * the lock is released, the returned element should be populated. */ + ecs_http_send_queue_t *sq = &srv->send_queue; + int32_t next = (sq->head + 1) % ECS_HTTP_SEND_QUEUE_MAX; + if (next == sq->tail) { + return NULL; + } + + /* Don't enqueue new requests if server is shutting down */ + if (!srv->should_run) { + return NULL; + } + + /* Return element at end of the queue */ + ecs_http_send_request_t *result = &sq->requests[sq->head]; + sq->head = next; + return result; +} + +static +ecs_http_send_request_t* http_send_queue_get( + ecs_http_server_t *srv) +{ + ecs_os_mutex_lock(srv->lock); + ecs_http_send_queue_t *sq = &srv->send_queue; + if (sq->tail == sq->head) { + return NULL; + } + + int32_t next = (sq->tail + 1) % ECS_HTTP_SEND_QUEUE_MAX; + ecs_http_send_request_t *result = &sq->requests[sq->tail]; + sq->tail = next; + return result; +} + +static +void* http_server_send_queue(void* arg) { + ecs_http_server_t *srv = arg; + int32_t wait_ms = srv->send_queue.wait_ms; + + /* Run for as long as the server is running or there are messages. When the + * server is stopping, no new messages will be enqueued */ + while (srv->should_run || (srv->send_queue.head != srv->send_queue.tail)) { + ecs_http_send_request_t* r = http_send_queue_get(srv); + if (!r) { + ecs_os_mutex_unlock(srv->lock); + /* If the queue is empty, wait so we don't run too fast */ + if (srv->should_run) { + ecs_os_sleep(0, wait_ms * 1000 * 1000); + } + } else { + ecs_http_socket_t sock = r->sock; + char *headers = r->headers; + int32_t headers_length = r->header_length; + char *content = r->content; + int32_t content_length = r->content_length; + ecs_os_mutex_unlock(srv->lock); + + if (http_socket_is_valid(sock)) { + bool error = false; + + http_sock_nonblock(sock, false); + + /* Write headers */ + ecs_size_t written = http_send(sock, headers, headers_length, 0); + if (written != headers_length) { + ecs_err("http: failed to write HTTP response headers: %s", + ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + error = true; + } else if (content_length >= 0) { + /* Write content */ + written = http_send(sock, content, content_length, 0); + if (written != content_length) { + ecs_err("http: failed to write HTTP response body: %s", + ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + error = true; + } + } + if (!error) { + ecs_os_linc(&ecs_http_send_ok_count); + } + + http_close(&sock); + } else { + ecs_err("http: invalid socket\n"); + } + + ecs_os_free(content); + ecs_os_free(headers); + } + } + return NULL; +} + +static +void http_append_send_headers( + ecs_strbuf_t *hdrs, + int code, + const char* status, + const char* content_type, + ecs_strbuf_t *extra_headers, + ecs_size_t content_len, + bool preflight) +{ + ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); + ecs_strbuf_appendint(hdrs, code); + ecs_strbuf_appendch(hdrs, ' '); + ecs_strbuf_appendstr(hdrs, status); + ecs_strbuf_appendlit(hdrs, "\r\n"); + + if (content_type) { + ecs_strbuf_appendlit(hdrs, "Content-Type: "); + ecs_strbuf_appendstr(hdrs, content_type); + ecs_strbuf_appendlit(hdrs, "\r\n"); + } + + if (content_len >= 0) { + ecs_strbuf_appendlit(hdrs, "Content-Length: "); + ecs_strbuf_append(hdrs, "%d", content_len); + ecs_strbuf_appendlit(hdrs, "\r\n"); + } + + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); + if (preflight) { + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, OPTIONS\r\n"); + ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); + } + + ecs_strbuf_mergebuff(hdrs, extra_headers); + + ecs_strbuf_appendlit(hdrs, "\r\n"); +} + +static +void http_send_reply( + ecs_http_connection_impl_t* conn, + ecs_http_reply_t* reply, + bool preflight) +{ + ecs_strbuf_t hdrs = ECS_STRBUF_INIT; + int32_t content_length = reply->body.length; + char *content = ecs_strbuf_get(&reply->body); + + /* Use asynchronous send queue for outgoing data so send operations won't + * hold up main thread */ + ecs_http_send_request_t *req = NULL; + + if (!preflight) { + req = http_send_queue_post(conn->pub.server); + if (!req) { + reply->code = 503; /* queue full, server is busy */ + ecs_os_linc(&ecs_http_busy_count); + } + } + + http_append_send_headers(&hdrs, reply->code, reply->status, + reply->content_type, &reply->headers, content_length, preflight); + ecs_size_t headers_length = ecs_strbuf_written(&hdrs); + char *headers = ecs_strbuf_get(&hdrs); + + if (!req) { + ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); + if (written != headers_length) { + ecs_err("http: failed to send reply to '%s:%s': %s", + conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + } + ecs_os_free(content); + ecs_os_free(headers); + http_close(&conn->sock); + return; + } + + /* Second, enqueue send request for response body */ + req->sock = conn->sock; + req->headers = headers; + req->header_length = headers_length; + req->content = content; + req->content_length = content_length; + + /* Take ownership of values */ + reply->body.content = NULL; + conn->sock = HTTP_SOCKET_INVALID; +} + +static +void http_recv_connection( + ecs_http_server_t *srv, + ecs_http_connection_impl_t *conn, + uint64_t conn_id, + ecs_http_socket_t sock) +{ + ecs_size_t bytes_read; + char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + ecs_http_fragment_t frag = {0}; + int32_t retries = 0; + + do { + if ((bytes_read = http_recv( + sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) + { + bool is_alive = conn->pub.id == conn_id; + if (!is_alive) { + /* Connection has been purged by main thread */ + goto done; + } + + if (http_parse_request(&frag, recv_buf, bytes_read)) { + if (frag.method == EcsHttpOptions) { + ecs_http_reply_t reply; + reply.body = ECS_STRBUF_INIT; + reply.code = 200; + reply.content_type = NULL; + reply.headers = ECS_STRBUF_INIT; + reply.status = "OK"; + http_send_reply(conn, &reply, true); + ecs_os_linc(&ecs_http_request_preflight_count); + } else { + ecs_http_request_entry_t *entry = + http_enqueue_request(conn, conn_id, &frag); + if (entry) { + ecs_http_reply_t reply; + reply.body = ECS_STRBUF_INIT; + reply.code = entry->code; + reply.content_type = "application/json"; + reply.headers = ECS_STRBUF_INIT; + reply.status = "OK"; + ecs_strbuf_appendstrn(&reply.body, + entry->content, entry->content_length); + http_send_reply(conn, &reply, false); + http_connection_free(conn); + + /* Lock was transferred from enqueue_request */ + ecs_os_mutex_unlock(srv->lock); + } + } + } else { + ecs_os_linc(&ecs_http_request_invalid_count); + } + } + + ecs_os_sleep(0, 10 * 1000 * 1000); + } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); + + if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { + http_close(&sock); + } + +done: + ecs_strbuf_reset(&frag.buf); +} + +typedef struct { + ecs_http_connection_impl_t *conn; + uint64_t id; +} http_conn_res_t; + +static +http_conn_res_t http_init_connection( + ecs_http_server_t *srv, + ecs_http_socket_t sock_conn, + struct sockaddr_storage *remote_addr, + ecs_size_t remote_addr_len) +{ + http_sock_set_timeout(sock_conn, 100); + http_sock_keep_alive(sock_conn); + http_sock_nonblock(sock_conn, true); + + /* Create new connection */ + ecs_os_mutex_lock(srv->lock); + ecs_http_connection_impl_t *conn = flecs_sparse_add_t( + &srv->connections, ecs_http_connection_impl_t); + uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); + conn->pub.server = srv; + conn->sock = sock_conn; + ecs_os_mutex_unlock(srv->lock); + + char *remote_host = conn->pub.host; + char *remote_port = conn->pub.port; + + /* Fetch name & port info */ + if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, + remote_host, ECS_SIZEOF(conn->pub.host), + remote_port, ECS_SIZEOF(conn->pub.port), + NI_NUMERICHOST | NI_NUMERICSERV)) + { + ecs_os_strcpy(remote_host, "unknown"); + ecs_os_strcpy(remote_port, "unknown"); + } + + ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", + remote_host, remote_port, sock_conn); + + return (http_conn_res_t){ .conn = conn, .id = conn_id }; +} + +static +void http_accept_connections( + ecs_http_server_t* srv, + const struct sockaddr* addr, + ecs_size_t addr_len) +{ +#ifdef ECS_TARGET_WINDOWS + /* If on Windows, test if winsock needs to be initialized */ + SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (INVALID_SOCKET == testsocket && WSANOTINITIALISED == WSAGetLastError()){ + WSADATA data = { 0 }; + int result = WSAStartup(MAKEWORD(2, 2), &data); + if (result) { + ecs_warn("http: WSAStartup failed with GetLastError = %d\n", + GetLastError()); + return; + } + } else { + http_close(&testsocket); + } +#endif + + /* Resolve name + port (used for logging) */ + char addr_host[256]; + char addr_port[20]; + + ecs_http_socket_t sock = HTTP_SOCKET_INVALID; + ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); + + if (http_getnameinfo( + addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, + ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) + { + ecs_os_strcpy(addr_host, "unknown"); + ecs_os_strcpy(addr_port, "unknown"); + } + + ecs_os_mutex_lock(srv->lock); + if (srv->should_run) { + ecs_dbg_2("http: initializing connection socket"); + + sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (!http_socket_is_valid(sock)) { + ecs_err("http: unable to create new connection socket: %s", + ecs_os_strerror(errno)); + ecs_os_mutex_unlock(srv->lock); + goto done; + } + + int reuse = 1, result; + result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char*)&reuse, ECS_SIZEOF(reuse)); + if (result) { + ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); + } + + if (addr->sa_family == AF_INET6) { + int ipv6only = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char*)&ipv6only, ECS_SIZEOF(ipv6only))) + { + ecs_warn("http: failed to setsockopt: %s", + ecs_os_strerror(errno)); + } + } + + result = http_bind(sock, addr, addr_len); + if (result) { + ecs_err("http: failed to bind to '%s:%s': %s", + addr_host, addr_port, ecs_os_strerror(errno)); + ecs_os_mutex_unlock(srv->lock); + goto done; + } + + http_sock_set_timeout(sock, 1000); + + srv->sock = sock; + + result = listen(srv->sock, SOMAXCONN); + if (result) { + ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", + SOMAXCONN, ecs_os_strerror(errno)); + } + + ecs_trace("http: listening for incoming connections on '%s:%s'", + addr_host, addr_port); + } else { + ecs_dbg_2("http: server shut down while initializing"); + } + ecs_os_mutex_unlock(srv->lock); + + struct sockaddr_storage remote_addr; + ecs_size_t remote_addr_len = 0; + + while (srv->should_run) { + remote_addr_len = ECS_SIZEOF(remote_addr); + ecs_http_socket_t sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, + &remote_addr_len); + + if (!http_socket_is_valid(sock_conn)) { + if (srv->should_run) { + ecs_dbg("http: connection attempt failed: %s", + ecs_os_strerror(errno)); + } + continue; + } + + http_conn_res_t conn = http_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); + http_recv_connection(srv, conn.conn, conn.id, sock_conn); + } + +done: + ecs_os_mutex_lock(srv->lock); + if (http_socket_is_valid(sock) && errno != EBADF) { + http_close(&sock); + srv->sock = sock; + } + ecs_os_mutex_unlock(srv->lock); + + ecs_trace("http: no longer accepting connections on '%s:%s'", + addr_host, addr_port); +} + +static +void* http_server_thread(void* arg) { + ecs_http_server_t *srv = arg; + struct sockaddr_in addr; + ecs_os_zeromem(&addr); + addr.sin_family = AF_INET; + addr.sin_port = htons(srv->port); + + if (!srv->ipaddr) { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); + } + + http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); + return NULL; +} + +static +void http_do_request( + ecs_http_server_t *srv, + ecs_http_reply_t *reply, + const ecs_http_request_impl_t *req) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->callback != NULL, ECS_INVALID_OPERATION, + "missing request handler for server"); + + if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, + srv->ctx) == false) + { + reply->code = 404; + reply->status = "Resource not found"; + ecs_os_linc(&ecs_http_request_not_handled_count); + } else { + if (reply->code >= 400) { + ecs_os_linc(&ecs_http_request_handled_error_count); + } else { + ecs_os_linc(&ecs_http_request_handled_ok_count); + } + } +error: + return; +} + +static +void http_handle_request( + ecs_http_server_t *srv, + ecs_http_request_impl_t *req) +{ + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + ecs_http_connection_impl_t *conn = + (ecs_http_connection_impl_t*)req->pub.conn; + + if (req->pub.method != EcsHttpOptions) { + if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { + reply.code = 404; + reply.status = "Resource not found"; + ecs_os_linc(&ecs_http_request_not_handled_count); + } else { + if (reply.code >= 400) { + ecs_os_linc(&ecs_http_request_handled_error_count); + } else { + ecs_os_linc(&ecs_http_request_handled_ok_count); + } + } + + if (req->pub.method == EcsHttpGet) { + http_insert_request_entry(srv, req, &reply); + } + + http_send_reply(conn, &reply, false); + ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); + } else { + /* Already taken care of */ + } + + http_reply_fini(&reply); + http_request_fini(req); + http_connection_free(conn); +} + +static +void http_purge_request_cache( + ecs_http_server_t *srv, + bool fini) +{ + ecs_time_t t = {0, 0}; + ecs_ftime_t time = (ecs_ftime_t)ecs_time_measure(&t); + ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); + while (ecs_map_next(&it)) { + ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); + int32_t i, count = ecs_vec_count(&bucket->values); + ecs_http_request_key_t *keys = ecs_vec_first(&bucket->keys); + ecs_http_request_entry_t *entries = ecs_vec_first(&bucket->values); + for (i = count - 1; i >= 0; i --) { + ecs_http_request_entry_t *entry = &entries[i]; + if (fini || ((time - entry->time) > srv->cache_purge_timeout)) { + ecs_http_request_key_t *key = &keys[i]; + /* Safe, code owns the value */ + ecs_os_free(ECS_CONST_CAST(char*, key->array)); + ecs_os_free(entry->content); + flecs_hm_bucket_remove(&srv->request_cache, bucket, + ecs_map_key(&it), i); + } + } + } + + if (fini) { + flecs_hashmap_fini(&srv->request_cache); + } +} + +static +int32_t http_dequeue_requests( + ecs_http_server_t *srv, + double delta_time) +{ + ecs_os_mutex_lock(srv->lock); + + int32_t i, request_count = flecs_sparse_count(&srv->requests); + for (i = request_count - 1; i >= 1; i --) { + ecs_http_request_impl_t *req = flecs_sparse_get_dense_t( + &srv->requests, ecs_http_request_impl_t, i); + http_handle_request(srv, req); + } + + int32_t connections_count = flecs_sparse_count(&srv->connections); + for (i = connections_count - 1; i >= 1; i --) { + ecs_http_connection_impl_t *conn = flecs_sparse_get_dense_t( + &srv->connections, ecs_http_connection_impl_t, i); + + conn->dequeue_timeout += delta_time; + conn->dequeue_retries ++; + + if ((conn->dequeue_timeout > + (double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && + (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) + { + ecs_dbg("http: purging connection '%s:%s' (sock = %d)", + conn->pub.host, conn->pub.port, conn->sock); + http_connection_free(conn); + } + } + + http_purge_request_cache(srv, false); + ecs_os_mutex_unlock(srv->lock); + + return request_count - 1; +} + +const char* ecs_http_get_header( + const ecs_http_request_t* req, + const char* name) +{ + for (ecs_size_t i = 0; i < req->header_count; i++) { + if (!ecs_os_strcmp(req->headers[i].key, name)) { + return req->headers[i].value; + } + } + return NULL; +} + +const char* ecs_http_get_param( + const ecs_http_request_t* req, + const char* name) +{ + for (ecs_size_t i = 0; i < req->param_count; i++) { + if (!ecs_os_strcmp(req->params[i].key, name)) { + return req->params[i].value; + } + } + return NULL; +} + +ecs_http_server_t* ecs_http_server_init( + const ecs_http_server_desc_t *desc) +{ + ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, + "missing OS API implementation"); + + ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); + srv->lock = ecs_os_mutex_new(); + srv->sock = HTTP_SOCKET_INVALID; + + srv->should_run = false; + srv->initialized = true; + + srv->cache_timeout = desc->cache_timeout; + srv->cache_purge_timeout = desc->cache_purge_timeout; + + if (!ECS_EQZERO(srv->cache_timeout) && + ECS_EQZERO(srv->cache_purge_timeout)) + { + srv->cache_purge_timeout = srv->cache_timeout * 10; + } + + srv->callback = desc->callback; + srv->ctx = desc->ctx; + srv->port = desc->port; + srv->ipaddr = desc->ipaddr; + srv->send_queue.wait_ms = desc->send_queue_wait_ms; + if (!srv->send_queue.wait_ms) { + srv->send_queue.wait_ms = 1; + } + + flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); + flecs_sparse_init_t(&srv->requests, NULL, NULL, ecs_http_request_impl_t); + + /* Start at id 1 */ + flecs_sparse_new_id(&srv->connections); + flecs_sparse_new_id(&srv->requests); + + /* Initialize request cache */ + flecs_hashmap_init(&srv->request_cache, + ecs_http_request_key_t, ecs_http_request_entry_t, + http_request_key_hash, http_request_key_compare, NULL); + +#ifndef ECS_TARGET_WINDOWS + /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client + * but te client already disconnected. */ + signal(SIGPIPE, SIG_IGN); +#endif + + return srv; +error: + return NULL; +} + +void ecs_http_server_fini( + ecs_http_server_t* srv) +{ + if (srv->should_run) { + ecs_http_server_stop(srv); + } + ecs_os_mutex_free(srv->lock); + http_purge_request_cache(srv, true); + flecs_sparse_fini(&srv->requests); + flecs_sparse_fini(&srv->connections); + ecs_os_free(srv); +} + +int ecs_http_server_start( + ecs_http_server_t *srv) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); + ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); + ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); + + srv->should_run = true; + + ecs_dbg("http: starting server thread"); + + srv->thread = ecs_os_thread_new(http_server_thread, srv); + if (!srv->thread) { + goto error; + } + + srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); + if (!srv->send_queue.thread) { + goto error; + } + + return 0; +error: + return -1; +} + +void ecs_http_server_stop( + ecs_http_server_t* srv) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_OPERATION, NULL); + ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); + + /* Stop server thread */ + ecs_dbg("http: shutting down server thread"); + + ecs_os_mutex_lock(srv->lock); + srv->should_run = false; + if (http_socket_is_valid(srv->sock)) { + http_close(&srv->sock); + } + ecs_os_mutex_unlock(srv->lock); + + ecs_os_thread_join(srv->thread); + ecs_os_thread_join(srv->send_queue.thread); + ecs_trace("http: server threads shut down"); + + /* Cleanup all outstanding requests */ + int i, count = flecs_sparse_count(&srv->requests); + for (i = count - 1; i >= 1; i --) { + http_request_fini(flecs_sparse_get_dense_t( + &srv->requests, ecs_http_request_impl_t, i)); + } + + /* Close all connections */ + count = flecs_sparse_count(&srv->connections); + for (i = count - 1; i >= 1; i --) { + http_connection_free(flecs_sparse_get_dense_t( + &srv->connections, ecs_http_connection_impl_t, i)); + } + + ecs_assert(flecs_sparse_count(&srv->connections) == 1, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_sparse_count(&srv->requests) == 1, + ECS_INTERNAL_ERROR, NULL); + + srv->thread = 0; +error: + return; +} + +void ecs_http_server_dequeue( + ecs_http_server_t* srv, + ecs_ftime_t delta_time) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); + + srv->dequeue_timeout += (double)delta_time; + srv->stats_timeout += (double)delta_time; + + if ((1000 * srv->dequeue_timeout) > (double)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { + srv->dequeue_timeout = 0; + + ecs_time_t t = {0}; + ecs_time_measure(&t); + int32_t request_count = http_dequeue_requests(srv, srv->dequeue_timeout); + srv->requests_processed += request_count; + srv->requests_processed_total += request_count; + double time_spent = ecs_time_measure(&t); + srv->request_time += time_spent; + srv->request_time_total += time_spent; + srv->dequeue_count ++; + } + + if ((1000 * srv->stats_timeout) > (double)ECS_HTTP_MIN_STATS_INTERVAL) { + srv->stats_timeout = 0; + ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", + srv->requests_processed, srv->request_time, + (srv->request_time / (double)srv->dequeue_count)); + srv->requests_processed = 0; + srv->request_time = 0; + srv->dequeue_count = 0; + } + +error: + return; +} + +int ecs_http_server_http_request( + ecs_http_server_t* srv, + const char *req, + ecs_size_t len, + ecs_http_reply_t *reply_out) +{ + if (!len) { + len = ecs_os_strlen(req); + } + + ecs_http_fragment_t frag = {0}; + if (!http_parse_request(&frag, req, len)) { + ecs_strbuf_reset(&frag.buf); + reply_out->code = 400; + return -1; + } + + ecs_http_request_impl_t request; + char *res = http_decode_request(&request, &frag); + if (!res) { + reply_out->code = 400; + return -1; + } + + ecs_http_request_entry_t *entry = + http_find_request_entry(srv, request.res, request.req_len); + if (entry) { + reply_out->body = ECS_STRBUF_INIT; + reply_out->code = entry->code; + reply_out->content_type = "application/json"; + reply_out->headers = ECS_STRBUF_INIT; + reply_out->status = "OK"; + ecs_strbuf_appendstrn(&reply_out->body, + entry->content, entry->content_length); + } else { + http_do_request(srv, reply_out, &request); + + if (request.pub.method == EcsHttpGet) { + http_insert_request_entry(srv, &request, reply_out); + } + } + + ecs_os_free(res); + + http_purge_request_cache(srv, false); + + return (reply_out->code >= 400) ? -1 : 0; +} + +int ecs_http_server_request( + ecs_http_server_t* srv, + const char *method, + const char *req, + ecs_http_reply_t *reply_out) +{ + const char *http_ver = " HTTP/1.1\r\n\r\n"; + int32_t method_len = ecs_os_strlen(method); + int32_t req_len = ecs_os_strlen(req); + int32_t http_ver_len = ecs_os_strlen(http_ver); + + int32_t len = method_len + req_len + http_ver_len + 1; + if (method_len + req_len + http_ver_len >= 1024) { + ecs_err("HTTP request too long"); + return -1; + } + + char reqstr[1024]; + char *ptr = reqstr; + ecs_os_memcpy(ptr, method, method_len); ptr += method_len; + ptr[0] = ' '; ptr ++; + ecs_os_memcpy(ptr, req, req_len); ptr += req_len; + ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; + ptr[0] = '\n'; + + return ecs_http_server_http_request(srv, reqstr, len, reply_out); +} + +void* ecs_http_server_ctx( + ecs_http_server_t* srv) +{ + return srv->ctx; +} + +#endif + +/** + * @file addons/journal.c + * @brief Journal addon. + */ + + +#ifdef FLECS_JOURNAL + +static +char* flecs_journal_entitystr( + ecs_world_t *world, + ecs_entity_t entity) +{ + char *path; + const char *_path = ecs_get_symbol(world, entity); + if (_path && !strchr(_path, '.')) { + path = ecs_asprintf("#[blue]%s", _path); + } else { + uint32_t gen = entity >> 32; + if (gen) { + path = ecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); + } else { + path = ecs_asprintf("#[normal]_%u", (uint32_t)entity); + } + } + return path; +} + +static +char* flecs_journal_idstr( + ecs_world_t *world, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + char *first_path = flecs_journal_entitystr(world, + ecs_pair_first(world, id)); + char *second_path = flecs_journal_entitystr(world, + ecs_pair_second(world, id)); + char *result = ecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", + first_path, second_path); + ecs_os_free(first_path); + ecs_os_free(second_path); + return result; + } else if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_journal_entitystr(world, id); + } else { + return ecs_id_str(world, id); + } +} + +static int flecs_journal_sp = 0; + +void flecs_journal_begin( + ecs_world_t *world, + ecs_journal_kind_t kind, + ecs_entity_t entity, + ecs_type_t *add, + ecs_type_t *remove) +{ + flecs_journal_sp ++; + + if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { + return; + } + + char *path = NULL; + char *var_id = NULL; + if (entity) { + if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) { + path = ecs_get_fullpath(world, entity); + var_id = flecs_journal_entitystr(world, entity); + } else { + path = ecs_id_str(world, entity); + var_id = flecs_journal_idstr(world, entity); + } + } + + if (kind == EcsJournalNew) { + ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); + ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); + ecs_print(4, "#[green]ecs_entity_t %s;", var_id); + ecs_print(4, "#[magenta]#endif"); + ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " + "#[grey] // %s = new()", var_id, path); + } + if (add) { + for (int i = 0; i < add->count; i ++) { + char *jidstr = flecs_journal_idstr(world, add->array[i]); + char *idstr = ecs_id_str(world, add->array[i]); + ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " + "#[grey] // add(%s, %s)", var_id, jidstr, + path, idstr); + ecs_os_free(idstr); + ecs_os_free(jidstr); + } + } + if (remove) { + for (int i = 0; i < remove->count; i ++) { + char *jidstr = flecs_journal_idstr(world, remove->array[i]); + char *idstr = ecs_id_str(world, remove->array[i]); + ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " + "#[grey] // remove(%s, %s)", var_id, jidstr, + path, idstr); + ecs_os_free(idstr); + ecs_os_free(jidstr); + } + } + if (kind == EcsJournalClear) { + ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " + "#[grey] // clear(%s)", var_id, path); + } else if (kind == EcsJournalDelete) { + ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " + "#[grey] // delete(%s)", var_id, path); + } else if (kind == EcsJournalDeleteWith) { + ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " + "#[grey] // delete_with(%s)", var_id, path); + } else if (kind == EcsJournalRemoveAll) { + ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " + "#[grey] // remove_all(%s)", var_id, path); + } else if (kind == EcsJournalTableEvents) { + ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " + "EcsAperiodicEmptyTables);"); + } + ecs_os_free(var_id); + ecs_os_free(path); + ecs_log_push(); +} + +void flecs_journal_end(void) { + flecs_journal_sp --; + ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_log_pop(); +} + +#endif + +/** + * @file addons/log.c + * @brief Log addon. + */ + + +#ifdef FLECS_LOG + +#include + +void flecs_colorize_buf( + char *msg, + bool enable_colors, + ecs_strbuf_t *buf) +{ + ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); + + char *ptr, ch, prev = '\0'; + bool isNum = false; + char isStr = '\0'; + bool isVar = false; + bool overrideColor = false; + bool autoColor = true; + bool dontAppend = false; + + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; + + if (!overrideColor) { + if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + isNum = false; + } + if (isStr && (isStr == ch) && prev != '\\') { + isStr = '\0'; + } else if (((ch == '\'') || (ch == '"')) && !isStr && + !isalpha(prev) && (prev != '\\')) + { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); + isStr = ch; + } + + if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || + (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && + !isalpha(prev) && !isdigit(prev) && (prev != '_') && + (prev != '.')) + { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); + isNum = true; + } + + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + isVar = false; + } + + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (enable_colors) ecs_strbuf_appendlit(buf, 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 (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); + } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); + } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); + } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); + } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); + } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); + } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); + } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); + } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { + overrideColor = false; + if (enable_colors) ecs_strbuf_appendlit(buf, 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 (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + overrideColor = false; + isNum = false; + isStr = false; + isVar = false; + } + } + + if (!dontAppend) { + ecs_strbuf_appendstrn(buf, ptr, 1); + } + + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } + } + + prev = ch; + } + + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } +} + +void ecs_printv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) +{ + (void)level; + (void)line; + + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + + /* Apply color. Even if we don't want color, we still need to call the + * colorize function to get rid of the color tags (e.g. #[green]) */ + char *msg_nocolor = ecs_vasprintf(fmt, args); + flecs_colorize_buf(msg_nocolor, + ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); + ecs_os_free(msg_nocolor); + + char *msg = ecs_strbuf_get(&msg_buf); + + if (msg) { + ecs_os_api.log_(level, file, line, msg); + ecs_os_free(msg); + } else { + ecs_os_api.log_(level, file, line, ""); + } +} + +void ecs_print_( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_printv_(level, file, line, fmt, args); + va_end(args); +} + +void ecs_logv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) +{ + if (level > ecs_os_api.log_level_) { + return; + } + + ecs_printv_(level, file, line, fmt, args); +} + +void ecs_log_( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (level > ecs_os_api.log_level_) { + return; + } + + va_list args; + va_start(args, fmt); + ecs_printv_(level, file, line, fmt, args); + va_end(args); +} + + +void ecs_log_push_( + int32_t level) +{ + if (level <= ecs_os_api.log_level_) { + ecs_os_api.log_indent_ ++; + } +} + +void ecs_log_pop_( + int32_t level) +{ + if (level <= ecs_os_api.log_level_) { + ecs_os_api.log_indent_ --; + ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); + } +} + +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) +{ + if (column_arg > 65536) { + /* Limit column size, which prevents the code from throwing up when the + * function is called with (expr - ptr), and expr is NULL. */ + column_arg = 0; + } + int32_t column = flecs_itoi32(column_arg); + + if (ecs_os_api.log_level_ >= -2) { + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + + ecs_strbuf_vappend(&msg_buf, fmt, args); + + if (expr) { + ecs_strbuf_appendch(&msg_buf, '\n'); + + /* Find start of line by taking column and looking for the + * last occurring newline */ + if (column != -1) { + const char *ptr = &expr[column]; + while (ptr[0] != '\n' && ptr > expr) { + ptr --; + } + + if (ptr == expr) { + /* ptr is already at start of line */ + } else { + column -= (int32_t)(ptr - expr + 1); + expr = ptr + 1; + } + } + + /* Strip newlines from current statement, if any */ + char *newline_ptr = strchr(expr, '\n'); + if (newline_ptr) { + /* Strip newline from expr */ + ecs_strbuf_appendstrn(&msg_buf, expr, + (int32_t)(newline_ptr - expr)); + } else { + ecs_strbuf_appendstr(&msg_buf, expr); + } + + ecs_strbuf_appendch(&msg_buf, '\n'); + + if (column != -1) { + int32_t c; + for (c = 0; c < column; c ++) { + ecs_strbuf_appendch(&msg_buf, ' '); + } + ecs_strbuf_appendch(&msg_buf, '^'); + } + } + + char *msg = ecs_strbuf_get(&msg_buf); + ecs_os_err(name, 0, msg); + ecs_os_free(msg); + } +} + +void ecs_parser_error_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + if (ecs_os_api.log_level_ >= -2) { + va_list args; + va_start(args, fmt); + ecs_parser_errorv_(name, expr, column, fmt, args); + va_end(args); + } +} + +void ecs_abort_( + int32_t err, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + va_end(args); + ecs_fatal_(file, line, "%s (%s)", msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + ecs_fatal_(file, line, "%s", ecs_strerror(err)); + } + ecs_os_api.log_last_error_ = err; +} + +void ecs_assert_log_( + int32_t err, + const char *cond_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + va_end(args); + ecs_fatal_(file, line, "assert: %s %s (%s)", + cond_str, msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + ecs_fatal_(file, line, "assert: %s %s", + cond_str, ecs_strerror(err)); + } + ecs_os_api.log_last_error_ = err; +} + +void ecs_deprecated_( + const char *file, + int32_t line, + const char *msg) +{ + ecs_err_(file, line, "%s", msg); +} + +bool ecs_should_log(int32_t level) { +# if !defined(FLECS_LOG_3) + if (level == 3) { + return false; + } +# endif +# if !defined(FLECS_LOG_2) + if (level == 2) { + return false; + } +# endif +# if !defined(FLECS_LOG_1) + if (level == 1) { + return false; + } +# endif + + return level <= ecs_os_api.log_level_; +} + +#define ECS_ERR_STR(code) case code: return &(#code[4]) + +const char* ecs_strerror( + int32_t error_code) +{ + switch (error_code) { + ECS_ERR_STR(ECS_INVALID_PARAMETER); + ECS_ERR_STR(ECS_NOT_A_COMPONENT); + ECS_ERR_STR(ECS_INTERNAL_ERROR); + ECS_ERR_STR(ECS_ALREADY_DEFINED); + ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); + ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); + ECS_ERR_STR(ECS_NAME_IN_USE); + ECS_ERR_STR(ECS_OUT_OF_MEMORY); + ECS_ERR_STR(ECS_DOUBLE_FREE); + ECS_ERR_STR(ECS_OPERATION_FAILED); + ECS_ERR_STR(ECS_INVALID_CONVERSION); + ECS_ERR_STR(ECS_MODULE_UNDEFINED); + ECS_ERR_STR(ECS_MISSING_SYMBOL); + ECS_ERR_STR(ECS_ALREADY_IN_USE); + ECS_ERR_STR(ECS_CYCLE_DETECTED); + ECS_ERR_STR(ECS_LEAK_DETECTED); + ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); + ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); + ECS_ERR_STR(ECS_COLUMN_IS_SHARED); + ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); + ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); + ECS_ERR_STR(ECS_INVALID_FROM_WORKER); + ECS_ERR_STR(ECS_OUT_OF_RANGE); + ECS_ERR_STR(ECS_MISSING_OS_API); + ECS_ERR_STR(ECS_UNSUPPORTED); + ECS_ERR_STR(ECS_ACCESS_VIOLATION); + ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); + ECS_ERR_STR(ECS_INCONSISTENT_NAME); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); + ECS_ERR_STR(ECS_INVALID_OPERATION); + ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); + ECS_ERR_STR(ECS_LOCKED_STORAGE); + ECS_ERR_STR(ECS_ID_IN_USE); + } + + return "unknown error code"; +} + +#else + +/* Empty bodies for when logging is disabled */ + +void ecs_log_( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)level; + (void)file; + (void)line; + (void)fmt; +} + +void ecs_parser_error_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; +} + +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; +} + +void ecs_abort_( + int32_t error_code, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)error_code; + (void)file; + (void)line; + (void)fmt; +} + +void ecs_assert_log_( + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)error_code; + (void)condition_str; + (void)file; + (void)line; + (void)fmt; +} + +#endif + +int ecs_log_get_level(void) { + return ecs_os_api.log_level_; +} + +int ecs_log_set_level( + int level) +{ + int prev = level; + ecs_os_api.log_level_ = level; + return prev; +} + +bool ecs_log_enable_colors( + bool enabled) +{ + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); + return prev; +} + +bool ecs_log_enable_timestamp( + bool enabled) +{ + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); + return prev; +} + +bool ecs_log_enable_timedelta( + bool enabled) +{ + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); + return prev; +} + +int ecs_log_last_error(void) +{ + int result = ecs_os_api.log_last_error_; + ecs_os_api.log_last_error_ = 0; + return result; +} + +/** + * @file addons/meta_c.c + * @brief C utilities for meta addon. + */ + + +#ifdef FLECS_META_C + +#include + +#define ECS_META_IDENTIFIER_LENGTH (256) + +#define ecs_meta_error(ctx, ptr, ...)\ + ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); + +typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; + +typedef struct meta_parse_ctx_t { + const char *name; + const char *desc; +} meta_parse_ctx_t; + +typedef struct meta_type_t { + ecs_meta_token_t type; + ecs_meta_token_t params; + bool is_const; + bool is_ptr; +} meta_type_t; + +typedef struct meta_member_t { + meta_type_t type; + ecs_meta_token_t name; + int64_t count; + bool is_partial; +} meta_member_t; + +typedef struct meta_constant_t { + ecs_meta_token_t name; + int64_t value; + bool is_value_set; +} meta_constant_t; + +typedef struct meta_params_t { + meta_type_t key_type; + meta_type_t type; + int64_t count; + bool is_key_value; + bool is_fixed_size; +} meta_params_t; + +static +const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) { + /* Keep track of which characters were used to open the scope */ + char stack[256]; + int32_t sp = 0; + char ch; + + while ((ch = *ptr)) { + if (ch == '(' || ch == '<') { + stack[sp] = ch; + + sp ++; + if (sp >= 256) { + ecs_meta_error(ctx, ptr, "maximum level of nesting reached"); + goto error; + } + } else if (ch == ')' || ch == '>') { + sp --; + if ((sp < 0) || (ch == '>' && stack[sp] != '<') || + (ch == ')' && stack[sp] != '(')) + { + ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch); + goto error; + } + } + + ptr ++; + + if (!sp) { + break; + } + } + + return ptr; +error: + return NULL; +} + +static +const char* parse_c_digit( + const char *ptr, + int64_t *value_out) +{ + char token[24]; + ptr = ecs_parse_ws_eol(ptr); + ptr = ecs_parse_digit(ptr, token); + if (!ptr) { + goto error; + } + + *value_out = strtol(token, NULL, 0); + + return ecs_parse_ws_eol(ptr); +error: + return NULL; +} + +static +const char* parse_c_identifier( + const char *ptr, + char *buff, + char *params, + meta_parse_ctx_t *ctx) +{ + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + char *bptr = buff, ch; + + if (params) { + params[0] = '\0'; + } + + /* Ignore whitespaces */ + ptr = ecs_parse_ws_eol(ptr); + ch = *ptr; + + if (!isalpha(ch) && (ch != '_')) { + ecs_meta_error(ctx, ptr, "invalid identifier (starts with '%c')", ch); + goto error; + } + + while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && + ch != '>' && ch != '}' && ch != '*') + { + /* Type definitions can contain macros or templates */ + if (ch == '(' || ch == '<') { + if (!params) { + ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); + goto error; + } + + const char *end = skip_scope(ptr, ctx); + ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); + params[end - ptr] = '\0'; + + ptr = end; + } else { + *bptr = ch; + bptr ++; + ptr ++; + } + } + + *bptr = '\0'; + + if (!ch) { + ecs_meta_error(ctx, ptr, "unexpected end of token"); + goto error; + } + + return ptr; +error: + return NULL; +} + +static +const char * meta_open_scope( + const char *ptr, + meta_parse_ctx_t *ctx) +{ + /* Skip initial whitespaces */ + ptr = ecs_parse_ws_eol(ptr); + + /* Is this the start of the type definition? */ + if (ctx->desc == ptr) { + if (*ptr != '{') { + ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); + goto error; + } + + ptr ++; + ptr = ecs_parse_ws_eol(ptr); + } + + /* Is this the end of the type definition? */ + if (!*ptr) { + ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); + goto error; + } + + /* Is this the end of the type definition? */ + if (*ptr == '}') { + ptr = ecs_parse_ws_eol(ptr + 1); + if (*ptr) { + ecs_meta_error(ctx, ptr, + "stray characters after struct definition"); + goto error; + } + return NULL; + } + + return ptr; +error: + return NULL; +} + +static +const char* meta_parse_constant( + const char *ptr, + meta_constant_t *token, + meta_parse_ctx_t *ctx) +{ + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } + + token->is_value_set = false; + + /* Parse token, constant identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + return NULL; + } + + ptr = ecs_parse_ws_eol(ptr); + if (!ptr) { + return NULL; + } + + /* Explicit value assignment */ + if (*ptr == '=') { + int64_t value = 0; + ptr = parse_c_digit(ptr + 1, &value); + token->value = value; + token->is_value_set = true; + } + + /* Expect a ',' or '}' */ + if (*ptr != ',' && *ptr != '}') { + ecs_meta_error(ctx, ptr, "missing , after enum constant"); + goto error; + } + + if (*ptr == ',') { + return ptr + 1; + } else { + return ptr; + } +error: + return NULL; +} + +static +const char* meta_parse_type( + const char *ptr, + meta_type_t *token, + meta_parse_ctx_t *ctx) +{ + token->is_ptr = false; + token->is_const = false; + + ptr = ecs_parse_ws_eol(ptr); + + /* Parse token, expect type identifier or ECS_PROPERTY */ + ptr = parse_c_identifier(ptr, token->type, token->params, ctx); + if (!ptr) { + goto error; + } + + if (!strcmp(token->type, "ECS_PRIVATE")) { + /* Members from this point are not stored in metadata */ + ptr += ecs_os_strlen(ptr); + goto done; + } + + /* If token is const, set const flag and continue parsing type */ + if (!strcmp(token->type, "const")) { + token->is_const = true; + + /* Parse type after const */ + ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); + } + + /* Check if type is a pointer */ + ptr = ecs_parse_ws_eol(ptr); + if (*ptr == '*') { + token->is_ptr = true; + ptr ++; + } + +done: + return ptr; +error: + return NULL; +} + +static +const char* meta_parse_member( + const char *ptr, + meta_member_t *token, + meta_parse_ctx_t *ctx) +{ + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } + + token->count = 1; + token->is_partial = false; + + /* Parse member type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + token->is_partial = true; + goto error; + } + + if (!ptr[0]) { + return ptr; + } + + /* Next token is the identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + goto error; + } + + /* Skip whitespace between member and [ or ; */ + ptr = ecs_parse_ws_eol(ptr); + + /* Check if this is an array */ + char *array_start = strchr(token->name, '['); + if (!array_start) { + /* If the [ was separated by a space, it will not be parsed as part of + * the name */ + if (*ptr == '[') { + /* safe, will not be modified */ + array_start = ECS_CONST_CAST(char*, ptr); + } + } + + if (array_start) { + /* Check if the [ matches with a ] */ + char *array_end = strchr(array_start, ']'); + if (!array_end) { + ecs_meta_error(ctx, ptr, "missing ']'"); + goto error; + + } else if (array_end - array_start == 0) { + ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); + goto error; + } + + token->count = atoi(array_start + 1); + + if (array_start == ptr) { + /* If [ was found after name, continue parsing after ] */ + ptr = array_end + 1; + } else { + /* If [ was fonud in name, replace it with 0 terminator */ + array_start[0] = '\0'; + } + } + + /* Expect a ; */ + if (*ptr != ';') { + ecs_meta_error(ctx, ptr, "missing ; after member declaration"); + goto error; + } + + return ptr + 1; +error: + return NULL; +} + +static +int meta_parse_desc( + const char *ptr, + meta_params_t *token, + meta_parse_ctx_t *ctx) +{ + token->is_key_value = false; + token->is_fixed_size = false; + + ptr = ecs_parse_ws_eol(ptr); + if (*ptr != '(' && *ptr != '<') { + ecs_meta_error(ctx, ptr, + "expected '(' at start of collection definition"); + goto error; + } + + ptr ++; + + /* Parse type identifier */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + goto error; + } + + ptr = ecs_parse_ws_eol(ptr); + + /* If next token is a ',' the first type was a key type */ + if (*ptr == ',') { + ptr = ecs_parse_ws_eol(ptr + 1); + + if (isdigit(*ptr)) { + int64_t value; + ptr = parse_c_digit(ptr, &value); + if (!ptr) { + goto error; + } + + token->count = value; + token->is_fixed_size = true; + } else { + token->key_type = token->type; + + /* Parse element type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + ptr = ecs_parse_ws_eol(ptr); + + token->is_key_value = true; + } + } + + if (*ptr != ')' && *ptr != '>') { + ecs_meta_error(ctx, ptr, + "expected ')' at end of collection definition"); + goto error; + } + + return 0; +error: + return -1; +} + +static +ecs_entity_t meta_lookup( + ecs_world_t *world, + meta_type_t *token, + const char *ptr, + int64_t count, + meta_parse_ctx_t *ctx); + +static +ecs_entity_t meta_lookup_array( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + if (!params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, "missing size for array"); + goto error; + } + + if (!params.count) { + ecs_meta_error(ctx, params_decl, "invalid array size"); + goto error; + } + + ecs_entity_t element_type = ecs_lookup_symbol( + world, params.type.type, true, true); + if (!element_type) { + ecs_meta_error(ctx, params_decl, "unknown element type '%s'", + params.type.type); + } + + if (!e) { + e = ecs_new_id(world); + } + + ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + + return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); +error: + return 0; +} + +static +ecs_entity_t meta_lookup_vector( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for vector"); + goto error; + } + + ecs_entity_t element_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); + + if (!e) { + e = ecs_new_id(world); + } + + return ecs_set(world, e, EcsVector, { element_type }); +error: + return 0; +} + +static +ecs_entity_t meta_lookup_bitmask( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + (void)e; + + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for bitmask"); + goto error; + } + + if (params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, + "unexpected size for bitmask"); + goto error; + } + + ecs_entity_t bitmask_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); + ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); + +#ifndef FLECS_NDEBUG + /* Make sure this is a bitmask type */ + const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType); + ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); +#endif + + return bitmask_type; +error: + return 0; +} + +static +ecs_entity_t meta_lookup( + ecs_world_t *world, + meta_type_t *token, + const char *ptr, + int64_t count, + meta_parse_ctx_t *ctx) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *typename = token->type; + ecs_entity_t type = 0; + + /* Parse vector type */ + if (!token->is_ptr) { + if (!ecs_os_strcmp(typename, "ecs_array")) { + type = meta_lookup_array(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "ecs_vector") || + !ecs_os_strcmp(typename, "flecs::vector")) + { + type = meta_lookup_vector(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { + type = meta_lookup_bitmask(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "flecs::byte")) { + type = ecs_id(ecs_byte_t); + + } else if (!ecs_os_strcmp(typename, "char")) { + type = ecs_id(ecs_char_t); + + } else if (!ecs_os_strcmp(typename, "bool") || + !ecs_os_strcmp(typename, "_Bool")) + { + type = ecs_id(ecs_bool_t); + + } else if (!ecs_os_strcmp(typename, "int8_t")) { + type = ecs_id(ecs_i8_t); + } else if (!ecs_os_strcmp(typename, "int16_t")) { + type = ecs_id(ecs_i16_t); + } else if (!ecs_os_strcmp(typename, "int32_t")) { + type = ecs_id(ecs_i32_t); + } else if (!ecs_os_strcmp(typename, "int64_t")) { + type = ecs_id(ecs_i64_t); + + } else if (!ecs_os_strcmp(typename, "uint8_t")) { + type = ecs_id(ecs_u8_t); + } else if (!ecs_os_strcmp(typename, "uint16_t")) { + type = ecs_id(ecs_u16_t); + } else if (!ecs_os_strcmp(typename, "uint32_t")) { + type = ecs_id(ecs_u32_t); + } else if (!ecs_os_strcmp(typename, "uint64_t")) { + type = ecs_id(ecs_u64_t); + + } else if (!ecs_os_strcmp(typename, "float")) { + type = ecs_id(ecs_f32_t); + } else if (!ecs_os_strcmp(typename, "double")) { + type = ecs_id(ecs_f64_t); + + } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { + type = ecs_id(ecs_entity_t); + + } else if (!ecs_os_strcmp(typename, "ecs_id_t")) { + type = ecs_id(ecs_id_t); + + } else if (!ecs_os_strcmp(typename, "char*")) { + type = ecs_id(ecs_string_t); + } else { + type = ecs_lookup_symbol(world, typename, true, true); + } + } else { + if (!ecs_os_strcmp(typename, "char")) { + typename = "flecs.meta.string"; + } else + if (token->is_ptr) { + typename = "flecs.meta.uptr"; + } else + if (!ecs_os_strcmp(typename, "char*") || + !ecs_os_strcmp(typename, "flecs::string")) + { + typename = "flecs.meta.string"; + } + + type = ecs_lookup_symbol(world, typename, true, true); + } + + if (count != 1) { + ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + + type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); + } + + if (!type) { + ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); + goto error; + } + + return type; +error: + return 0; +} + +static +int meta_parse_struct( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + const char *ptr = desc; + const char *name = ecs_get_name(world, t); + + meta_member_t token; + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { + ecs_entity_t m = ecs_entity(world, { + .name = token.name + }); + + ecs_entity_t type = meta_lookup( + world, &token.type, ptr, 1, &ctx); + if (!type) { + goto error; + } + + ecs_set(world, m, EcsMember, { + .type = type, + .count = (ecs_size_t)token.count + }); + } + + ecs_set_scope(world, old_scope); + + return 0; +error: + return -1; +} + +static +int meta_parse_constants( + ecs_world_t *world, + ecs_entity_t t, + const char *desc, + bool is_bitmask) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *ptr = desc; + const char *name = ecs_get_name(world, t); + int32_t name_len = ecs_os_strlen(name); + const ecs_world_info_t *info = ecs_get_world_info(world); + const char *name_prefix = info->name_prefix; + int32_t name_prefix_len = name_prefix ? ecs_os_strlen(name_prefix) : 0; + + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; + + meta_constant_t token; + int64_t last_value = 0; + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { + if (token.is_value_set) { + last_value = token.value; + } else if (is_bitmask) { + ecs_meta_error(&ctx, ptr, + "bitmask requires explicit value assignment"); + goto error; + } + + if (name_prefix) { + if (!ecs_os_strncmp(token.name, name_prefix, name_prefix_len)) { + ecs_os_memmove(token.name, token.name + name_prefix_len, + ecs_os_strlen(token.name) - name_prefix_len + 1); + } + } + + if (!ecs_os_strncmp(token.name, name, name_len)) { + ecs_os_memmove(token.name, token.name + name_len, + ecs_os_strlen(token.name) - name_len + 1); + } + + ecs_entity_t c = ecs_entity(world, { + .name = token.name + }); + + if (!is_bitmask) { + ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, + {(ecs_i32_t)last_value}); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, + {(ecs_u32_t)last_value}); + } + + last_value ++; + } + + ecs_set_scope(world, old_scope); + + return 0; +error: + return -1; +} + +static +int meta_parse_enum( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + ecs_add(world, t, EcsEnum); + return meta_parse_constants(world, t, desc, false); +} + +static +int meta_parse_bitmask( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + ecs_add(world, t, EcsBitmask); + return meta_parse_constants(world, t, desc, true); +} + +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc) +{ + switch(kind) { + case EcsStructType: + if (meta_parse_struct(world, component, desc)) { + goto error; + } + break; + case EcsEnumType: + if (meta_parse_enum(world, component, desc)) { + goto error; + } + break; + case EcsBitmaskType: + if (meta_parse_bitmask(world, component, desc)) { + goto error; + } + break; + case EcsPrimitiveType: + case EcsArrayType: + case EcsVectorType: + case EcsOpaqueType: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); + } + + return 0; +error: + return -1; +} + +#endif + +/** + * @file addons/metrics.c + * @brief Metrics addon. + */ + + +#ifdef FLECS_METRICS + +/* Public components */ +ECS_COMPONENT_DECLARE(FlecsMetrics); +ECS_TAG_DECLARE(EcsMetricInstance); +ECS_COMPONENT_DECLARE(EcsMetricValue); +ECS_COMPONENT_DECLARE(EcsMetricSource); +ECS_TAG_DECLARE(EcsMetric); +ECS_TAG_DECLARE(EcsCounter); +ECS_TAG_DECLARE(EcsCounterIncrement); +ECS_TAG_DECLARE(EcsCounterId); +ECS_TAG_DECLARE(EcsGauge); + +/* Internal components */ +static ECS_COMPONENT_DECLARE(EcsMetricMember); +static ECS_COMPONENT_DECLARE(EcsMetricId); +static ECS_COMPONENT_DECLARE(EcsMetricOneOf); +static ECS_COMPONENT_DECLARE(EcsMetricCountIds); +static ECS_COMPONENT_DECLARE(EcsMetricCountTargets); +static ECS_COMPONENT_DECLARE(EcsMetricMemberInstance); +static ECS_COMPONENT_DECLARE(EcsMetricIdInstance); +static ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance); + +/** Context for metric */ +typedef struct { + ecs_entity_t metric; /**< Metric entity */ + ecs_entity_t kind; /**< Metric kind (gauge, counter) */ +} ecs_metric_ctx_t; + +/** Context for metric that monitors member */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_primitive_kind_t type_kind; /**< Primitive type kind of member */ + uint16_t offset; /**< Offset of member in component */ +} ecs_member_metric_ctx_t; + +/** Context for metric that monitors whether entity has id */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_id_record_t *idr; /**< Id record for monitored component */ +} ecs_id_metric_ctx_t; + +/** Context for metric that monitors whether entity has pair target */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_id_record_t *idr; /**< Id record for monitored component */ + ecs_size_t size; /**< Size of metric type */ + ecs_map_t target_offset; /**< Pair target to metric type offset */ +} ecs_oneof_metric_ctx_t; + +/** Context for metric that monitors how many entities have a pair target */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_id_record_t *idr; /**< Id record for monitored component */ + ecs_map_t targets; /**< Map of counters for each target */ +} ecs_count_targets_metric_ctx_t; + +/** Stores context shared for all instances of member metric */ +typedef struct { + ecs_member_metric_ctx_t *ctx; +} EcsMetricMember; + +/** Stores context shared for all instances of id metric */ +typedef struct { + ecs_id_metric_ctx_t *ctx; +} EcsMetricId; + +/** Stores context shared for all instances of oneof metric */ +typedef struct { + ecs_oneof_metric_ctx_t *ctx; +} EcsMetricOneOf; + +/** Stores context shared for all instances of id counter metric */ +typedef struct { + ecs_id_t id; +} EcsMetricCountIds; + +/** Stores context shared for all instances of target counter metric */ +typedef struct { + ecs_count_targets_metric_ctx_t *ctx; +} EcsMetricCountTargets; + +/** Instance of member metric */ +typedef struct { + ecs_ref_t ref; + ecs_member_metric_ctx_t *ctx; +} EcsMetricMemberInstance; + +/** Instance of id metric */ +typedef struct { + ecs_record_t *r; + ecs_id_metric_ctx_t *ctx; +} EcsMetricIdInstance; + +/** Instance of oneof metric */ +typedef struct { + ecs_record_t *r; + ecs_oneof_metric_ctx_t *ctx; +} EcsMetricOneOfInstance; + +/** Component lifecycle */ + +static ECS_DTOR(EcsMetricMember, ptr, { + ecs_os_free(ptr->ctx); +}) + +static ECS_MOVE(EcsMetricMember, dst, src, { + *dst = *src; + src->ctx = NULL; +}) + +static ECS_DTOR(EcsMetricId, ptr, { + ecs_os_free(ptr->ctx); +}) + +static ECS_MOVE(EcsMetricId, dst, src, { + *dst = *src; + src->ctx = NULL; +}) + +static ECS_DTOR(EcsMetricOneOf, ptr, { + if (ptr->ctx) { + ecs_map_fini(&ptr->ctx->target_offset); + ecs_os_free(ptr->ctx); + } +}) + +static ECS_MOVE(EcsMetricOneOf, dst, src, { + *dst = *src; + src->ctx = NULL; +}) + +static ECS_DTOR(EcsMetricCountTargets, ptr, { + if (ptr->ctx) { + ecs_map_fini(&ptr->ctx->targets); + ecs_os_free(ptr->ctx); + } +}) + +static ECS_MOVE(EcsMetricCountTargets, dst, src, { + *dst = *src; + src->ctx = NULL; +}) + +/** Observer used for creating new instances of member metric */ +static void flecs_metrics_on_member_metric(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_member_metric_ctx_t *ctx = it->ctx; + ecs_id_t id = ecs_field_id(it, 1); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); + + EcsMetricMemberInstance *src = ecs_emplace( + world, m, EcsMetricMemberInstance); + src->ref = ecs_ref_init_id(world, e, id); + src->ctx = ctx; + ecs_modified(world, m, EcsMetricMemberInstance); + ecs_set(world, m, EcsMetricValue, { 0 }); + ecs_set(world, m, EcsMetricSource, { e }); + ecs_add(world, m, EcsMetricInstance); + ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); + } +} + +/** Observer used for creating new instances of id metric */ +static void flecs_metrics_on_id_metric(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_id_metric_ctx_t *ctx = it->ctx; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); + + EcsMetricIdInstance *src = ecs_emplace(world, m, EcsMetricIdInstance); + src->r = ecs_record_find(world, e); + src->ctx = ctx; + ecs_modified(world, m, EcsMetricIdInstance); + ecs_set(world, m, EcsMetricValue, { 0 }); + ecs_set(world, m, EcsMetricSource, { e }); + ecs_add(world, m, EcsMetricInstance); + ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); + } +} + +/** Observer used for creating new instances of oneof metric */ +static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) { + if (it->event == EcsOnRemove) { + return; + } + + ecs_world_t *world = it->world; + ecs_oneof_metric_ctx_t *ctx = it->ctx; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); + + EcsMetricOneOfInstance *src = ecs_emplace(world, m, EcsMetricOneOfInstance); + src->r = ecs_record_find(world, e); + src->ctx = ctx; + ecs_modified(world, m, EcsMetricOneOfInstance); + ecs_add_pair(world, m, ctx->metric.metric, ecs_id(EcsMetricValue)); + ecs_set(world, m, EcsMetricSource, { e }); + ecs_add(world, m, EcsMetricInstance); + ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); + } +} + +/** Set doc name of metric instance to name of source entity */ +#ifdef FLECS_DOC +static void SetMetricDocName(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t src_e = src[i].entity; + const char *name = ecs_get_name(world, src_e); + if (name) { + ecs_doc_set_name(world, it->entities[i], name); + } + } +} +#endif + +/** Delete metric instances for entities that are no longer alive */ +static void ClearMetricInstance(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t src_e = src[i].entity; + if (!ecs_is_alive(world, src_e)) { + ecs_delete(world, it->entities[i]); + } + } +} + +/** Update member metric */ +static void UpdateMemberInstance(ecs_iter_t *it, bool counter) { + ecs_world_t *world = it->real_world; + EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1); + EcsMetricMemberInstance *mi = ecs_field(it, EcsMetricMemberInstance, 2); + ecs_ftime_t dt = it->delta_time; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_member_metric_ctx_t *ctx = mi[i].ctx; + ecs_ref_t *ref = &mi[i].ref; + const void *ptr = ecs_ref_get_id(world, ref, ref->id); + if (ptr) { + ptr = ECS_OFFSET(ptr, ctx->offset); + if (!counter) { + m[i].value = ecs_meta_ptr_to_float(ctx->type_kind, ptr); + } else { + m[i].value += + ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt; + } + } else { + ecs_delete(it->world, it->entities[i]); + } + } +} + +static void UpdateGaugeMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, false); +} + +static void UpdateCounterMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, false); +} + +static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, true); +} + +/** Update id metric */ +static void UpdateIdInstance(ecs_iter_t *it, bool counter) { + ecs_world_t *world = it->real_world; + EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1); + EcsMetricIdInstance *mi = ecs_field(it, EcsMetricIdInstance, 2); + ecs_ftime_t dt = it->delta_time; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_table_t *table = mi[i].r->table; + if (!table) { + ecs_delete(it->world, it->entities[i]); + continue; + } + + ecs_id_metric_ctx_t *ctx = mi[i].ctx; + ecs_id_record_t *idr = ctx->idr; + if (flecs_search_w_idr(world, table, idr->id, NULL, idr) != -1) { + if (!counter) { + m[i].value = 1.0; + } else { + m[i].value += 1.0 * (double)dt; + } + } else { + ecs_delete(it->world, it->entities[i]); + } + } +} + +static void UpdateGaugeIdInstance(ecs_iter_t *it) { + UpdateIdInstance(it, false); +} + +static void UpdateCounterIdInstance(ecs_iter_t *it) { + UpdateIdInstance(it, true); +} + +/** Update oneof metric */ +static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { + ecs_world_t *world = it->real_world; + ecs_table_t *table = it->table; + void *m = ecs_table_get_column(table, + ecs_table_type_to_column_index(table, it->columns[0] - 1), it->offset); + EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 2); + ecs_ftime_t dt = it->delta_time; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; + ecs_table_t *mtable = mi[i].r->table; + + double *value = ECS_ELEM(m, ctx->size, i); + if (!counter) { + ecs_os_memset(value, 0, ctx->size); + } + + if (!mtable) { + ecs_delete(it->world, it->entities[i]); + continue; + } + + ecs_id_record_t *idr = ctx->idr; + ecs_id_t id; + if (flecs_search_w_idr(world, mtable, idr->id, &id, idr) == -1) { + ecs_delete(it->world, it->entities[i]); + continue; + } + + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + uint64_t *offset = ecs_map_get(&ctx->target_offset, tgt); + if (!offset) { + ecs_err("unexpected relationship target for metric"); + continue; + } + + value = ECS_OFFSET(value, *offset); + + if (!counter) { + *value = 1.0; + } else { + *value += 1.0 * (double)dt; + } + } +} + +static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { + UpdateOneOfInstance(it, false); +} + +static void UpdateCounterOneOfInstance(ecs_iter_t *it) { + UpdateOneOfInstance(it, true); +} + +static void UpdateCountTargets(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 1); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_count_targets_metric_ctx_t *ctx = m[i].ctx; + ecs_id_record_t *cur = ctx->idr; + while ((cur = cur->first.next)) { + ecs_id_t id = cur->id; + ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id); + if (!mi[0]) { + mi[0] = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); + ecs_entity_t tgt = ecs_pair_second(world, cur->id); + const char *name = ecs_get_name(world, tgt); + if (name) { + ecs_set_name(world, mi[0], name); + } + + EcsMetricSource *source = ecs_get_mut( + world, mi[0], EcsMetricSource); + source->entity = tgt; + } + + EcsMetricValue *value = ecs_get_mut(world, mi[0], EcsMetricValue); + value->value += (double)ecs_count_id(world, cur->id) * + (double)it->delta_system_time; + } + } +} + +static void UpdateCountIds(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsMetricCountIds *m = ecs_field(it, EcsMetricCountIds, 1); + EcsMetricValue *v = ecs_field(it, EcsMetricValue, 2); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + v[i].value += (double)ecs_count_id(world, m[i].id) * + (double)it->delta_system_time; + } +} + +/** Initialize member metric */ +static +int flecs_member_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) +{ + ecs_entity_t type = 0, member_type = 0, member = 0, id = 0; + uintptr_t offset = 0; + + if (desc->dotmember) { + if (!desc->id) { + char *metric_name = ecs_get_fullpath(world, metric); + ecs_err("missing id for metric '%s' with member '%s", + metric_name, desc->dotmember); + ecs_os_free(metric_name); + goto error; + } + + if (desc->member) { + char *metric_name = ecs_get_fullpath(world, metric); + ecs_err("cannot set both member and dotmember for metric '%s'", + metric_name); + ecs_os_free(metric_name); + goto error; + } + + type = ecs_get_typeid(world, desc->id); + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); + if (ecs_meta_push(&cur)) { + char *metric_name = ecs_get_fullpath(world, metric); + ecs_err("invalid type for metric '%s'", metric_name); + ecs_os_free(metric_name); + goto error; + } + if (ecs_meta_dotmember(&cur, desc->dotmember)) { + char *metric_name = ecs_get_fullpath(world, metric); + ecs_err("invalid dotmember '%s' for metric '%s'", + desc->dotmember, metric_name); + ecs_os_free(metric_name); + goto error; + } + + id = desc->id; + member_type = ecs_meta_get_type(&cur); + offset = (uintptr_t)ecs_meta_get_ptr(&cur); + member = ecs_meta_get_member_id(&cur); + } else { + const EcsMember *m = ecs_get(world, desc->member, EcsMember); + if (!m) { + char *metric_name = ecs_get_fullpath(world, metric); + char *member_name = ecs_get_fullpath(world, desc->member); + ecs_err("entity '%s' provided for metric '%s' is not a member", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + type = ecs_get_parent(world, desc->member); + if (!type) { + char *metric_name = ecs_get_fullpath(world, metric); + char *member_name = ecs_get_fullpath(world, desc->member); + ecs_err("member '%s' provided for metric '%s' is not part of a type", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + id = type; + if (desc->id) { + if (type != ecs_get_typeid(world, desc->id)) { + char *metric_name = ecs_get_fullpath(world, metric); + char *member_name = ecs_get_fullpath(world, desc->member); + char *id_name = ecs_get_fullpath(world, desc->id); + ecs_err("member '%s' for metric '%s' is not of type '%s'", + member_name, metric_name, id_name); + ecs_os_free(id_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + id = desc->id; + } + + member = desc->member; + member_type = m->type; + offset = flecs_ito(uintptr_t, m->offset); + } + + const EcsPrimitive *p = ecs_get(world, member_type, EcsPrimitive); + if (!p) { + char *metric_name = ecs_get_fullpath(world, metric); + char *member_name = ecs_get_fullpath(world, desc->member); + ecs_err("member '%s' provided for metric '%s' must have primitive type", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + const EcsMetaType *mt = ecs_get(world, type, EcsMetaType); + if (!mt) { + char *metric_name = ecs_get_fullpath(world, metric); + char *member_name = ecs_get_fullpath(world, desc->member); + ecs_err("parent of member '%s' for metric '%s' is not a type", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + if (mt->kind != EcsStructType) { + char *metric_name = ecs_get_fullpath(world, metric); + char *member_name = ecs_get_fullpath(world, desc->member); + ecs_err("parent of member '%s' for metric '%s' is not a struct", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + ecs_member_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_member_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->type_kind = p->kind; + ctx->offset = flecs_uto(uint16_t, offset); + + ecs_observer(world, { + .entity = metric, + .events = { EcsOnAdd }, + .filter.terms[0] = { + .id = id, + .src.flags = EcsSelf, + .inout = EcsInOutNone + }, + .callback = flecs_metrics_on_member_metric, + .yield_existing = true, + .ctx = ctx + }); + + ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); + + return 0; +error: + return -1; +} + +/** Update id metric */ +static +int flecs_id_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) +{ + ecs_id_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_id_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->idr = flecs_id_record_ensure(world, desc->id); + ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_observer(world, { + .entity = metric, + .events = { EcsOnAdd }, + .filter.terms[0] = { + .id = desc->id, + .src.flags = EcsSelf, + .inout = EcsInOutNone + }, + .callback = flecs_metrics_on_id_metric, + .yield_existing = true, + .ctx = ctx + }); + + ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); + + return 0; +error: + return -1; +} + +/** Update oneof metric */ +static +int flecs_oneof_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + ecs_entity_t scope, + const ecs_metric_desc_t *desc) +{ + ecs_oneof_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_oneof_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->idr = flecs_id_record_ensure(world, desc->id); + ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_map_init(&ctx->target_offset, NULL); + + /* Add member for each child of oneof to metric, so it can be used as metric + * instance type that holds values for all targets */ + ecs_iter_t it = ecs_children(world, scope); + uint64_t offset = 0; + while (ecs_children_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_entity_t tgt = it.entities[i]; + const char *name = ecs_get_name(world, tgt); + if (!name) { + /* Member must have name */ + continue; + } + + char *to_snake_case = flecs_to_snake_case(name); + + ecs_entity_t mbr = ecs_entity(world, { + .name = to_snake_case, + .add = { ecs_childof(metric) } + }); + + ecs_os_free(to_snake_case); + + ecs_set(world, mbr, EcsMember, { + .type = ecs_id(ecs_f64_t), + .unit = EcsSeconds + }); + + /* Truncate upper 32 bits of target so we can lookup the offset + * with the id we get from the pair */ + ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; + + offset += sizeof(double); + } + } + + ctx->size = flecs_uto(ecs_size_t, offset); + + ecs_observer(world, { + .entity = metric, + .events = { EcsMonitor }, + .filter.terms[0] = { + .id = desc->id, + .src.flags = EcsSelf, + .inout = EcsInOutNone + }, + .callback = flecs_metrics_on_oneof_metric, + .yield_existing = true, + .ctx = ctx + }); + + ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); + + return 0; +error: + return -1; +} + +static +int flecs_count_id_targets_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) +{ + ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->idr = flecs_id_record_ensure(world, desc->id); + ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_map_init(&ctx->targets, NULL); + + ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); + + return 0; +error: + return -1; +} + +static +int flecs_count_ids_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) +{ + ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); + ecs_set(world, metric, EcsMetricValue, { .value = 0 }); + return 0; +} + +ecs_entity_t ecs_metric_init( + ecs_world_t *world, + const ecs_metric_desc_t *desc) +{ + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(world, ecs_world_t); + + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new_id(world); + } + + ecs_entity_t kind = desc->kind; + if (!kind) { + ecs_err("missing metric kind"); + goto error; + } + + if (kind != EcsGauge && + kind != EcsCounter && + kind != EcsCounterId && + kind != EcsCounterIncrement) + { + ecs_err("invalid metric kind %s", ecs_get_fullpath(world, kind)); + goto error; + } + + if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { + ecs_err("CounterIncrement can only be used in combination with member"); + goto error; + } + + if (kind == EcsCounterId && (desc->member || desc->dotmember)) { + ecs_err("CounterId cannot be used in combination with member"); + goto error; + } + + if (desc->brief) { +#ifdef FLECS_DOC + ecs_doc_set_brief(world, result, desc->brief); +#else + ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief"); +#endif + } + + if (desc->member || desc->dotmember) { + if (flecs_member_metric_init(world, result, desc)) { + goto error; + } + } else if (desc->id) { + if (desc->targets) { + if (!ecs_id_is_pair(desc->id)) { + ecs_err("cannot specify targets for id that is not a pair"); + goto error; + } + if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { + ecs_err("first element of pair cannot be wildcard with " + " targets enabled"); + goto error; + } + if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { + ecs_err("second element of pair must be wildcard with " + " targets enabled"); + goto error; + } + + if (kind == EcsCounterId) { + if (flecs_count_id_targets_metric_init(world, result, desc)) { + goto error; + } + } else { + ecs_entity_t first = ecs_pair_first(world, desc->id); + ecs_entity_t scope = flecs_get_oneof(world, first); + if (!scope) { + ecs_err("first element of pair must have OneOf with " + " targets enabled"); + goto error; + } + + if (flecs_oneof_metric_init(world, result, scope, desc)) { + goto error; + } + } + } else { + if (kind == EcsCounterId) { + if (flecs_count_ids_metric_init(world, result, desc)) { + goto error; + } + } else { + if (flecs_id_metric_init(world, result, desc)) { + goto error; + } + } + } + } else { + ecs_err("missing source specified for metric"); + goto error; + } + + return result; +error: + if (result && result != desc->entity) { + ecs_delete(world, result); + } + return 0; +} + +void FlecsMetricsImport(ecs_world_t *world) { + ECS_MODULE_DEFINE(world, FlecsMetrics); + + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsMeta); + ECS_IMPORT(world, FlecsUnits); + + ecs_set_name_prefix(world, "Ecs"); + ECS_TAG_DEFINE(world, EcsMetric); + ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); + ECS_TAG_DEFINE(world, EcsCounter); + ECS_TAG_DEFINE(world, EcsCounterIncrement); + ECS_TAG_DEFINE(world, EcsCounterId); + ECS_TAG_DEFINE(world, EcsGauge); + ecs_set_scope(world, old_scope); + + ecs_set_name_prefix(world, "EcsMetric"); + ECS_TAG_DEFINE(world, EcsMetricInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricValue); + ECS_COMPONENT_DEFINE(world, EcsMetricSource); + ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricMember); + ECS_COMPONENT_DEFINE(world, EcsMetricId); + ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); + ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); + ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); + + ecs_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); + ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); + ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); + + ecs_struct(world, { + .entity = ecs_id(EcsMetricValue), + .members = { + { .name = "value", .type = ecs_id(ecs_f64_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsMetricSource), + .members = { + { .name = "entity", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_set_hooks(world, EcsMetricMember, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricMember), + .move = ecs_move(EcsMetricMember) + }); + + ecs_set_hooks(world, EcsMetricId, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricId), + .move = ecs_move(EcsMetricId) + }); + + ecs_set_hooks(world, EcsMetricOneOf, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricOneOf), + .move = ecs_move(EcsMetricOneOf) + }); + + ecs_set_hooks(world, EcsMetricCountTargets, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricCountTargets), + .move = ecs_move(EcsMetricCountTargets) + }); + + ecs_add_id(world, EcsMetric, EcsOneOf); + +#ifdef FLECS_DOC + ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, EcsMetricSource); +#endif + + ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, + [in] Source); + + ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, Gauge)); + + ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, Counter)); + + ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, CounterIncrement)); + + ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, + [out] Value, + [in] IdInstance, + [none] (Metric, Gauge)); + + ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, + [inout] Value, + [in] IdInstance, + [none] (Metric, Counter)); + + ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore, + [none] (_, Value), + [in] OneOfInstance, + [none] (Metric, Gauge)); + + ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore, + [none] (_, Value), + [in] OneOfInstance, + [none] (Metric, Counter)); + + ECS_SYSTEM(world, UpdateCountIds, EcsPreStore, + [inout] CountIds, Value); + + ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore, + [inout] CountTargets); +} + +#endif + +/** + * @file addons/module.c + * @brief Module addon. + */ + + +#ifdef FLECS_MODULE + +#include + +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 = flecs_ito(char, 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 module, + const char *module_name) +{ + ecs_check(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); + + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->info.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("#[magenta]import#[reset] %s", module_name); + ecs_log_push(); + + /* Load module */ + module(world); + + /* Lookup module entity (must be registered by module) */ + e = ecs_lookup_fullpath(world, module_name); + ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); + + ecs_log_pop(); + } + + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->info.name_prefix = old_name_prefix; + + return e; +error: + return 0; +} + +ecs_entity_t ecs_import_c( + ecs_world_t *world, + ecs_module_action_t module, + const char *c_name) +{ + char *name = ecs_module_path_from_c(c_name); + ecs_entity_t e = ecs_import(world, module, name); + ecs_os_free(name); + return e; +} + +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name) +{ + ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + + char *import_func = ECS_CONST_CAST(char*, module_name); + char *module = ECS_CONST_CAST(char*, module_name); + + if (!ecs_os_has_modules() || !ecs_os_has_dl()) { + ecs_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 = flecs_ito(char, toupper(ch)); + bptr ++; + capitalize = false; + } else { + *bptr = flecs_ito(char, 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_err("failed to find library file for '%s'", library_name); + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace("found file '%s' for library '%s'", + library_filename, library_name); + } + + ecs_os_dl_t dl = ecs_os_dlopen(library_filename); + if (!dl) { + ecs_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("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_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("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); + + 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; +error: + return 0; +} + +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const char *c_name, + const ecs_component_desc_t *desc) +{ + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(world, ecs_world_t); + + ecs_entity_t old_scope = ecs_set_scope(world, 0); + + ecs_entity_t e = desc->entity; + if (!e) { + char *module_path = ecs_module_path_from_c(c_name); + e = ecs_new_from_fullpath(world, module_path); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); + } else if (!ecs_exists(world, e)) { + char *module_path = ecs_module_path_from_c(c_name); + ecs_ensure(world, e); + ecs_add_fullpath(world, e, module_path); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); + } + + ecs_add_id(world, e, EcsModule); + + ecs_component_desc_t private_desc = *desc; + private_desc.entity = e; + + if (desc->type.size) { + ecs_entity_t result = ecs_component_init(world, &private_desc); + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); + (void)result; + } + + ecs_set_scope(world, old_scope); + + return e; +error: + return 0; +} + +#endif + +/** + * @file addons/monitor.c + * @brief Monitor addon. + */ + + +#ifdef FLECS_MONITOR + +ECS_COMPONENT_DECLARE(FlecsMonitor); +ECS_COMPONENT_DECLARE(EcsWorldStats); +ECS_COMPONENT_DECLARE(EcsWorldSummary); +ECS_COMPONENT_DECLARE(EcsPipelineStats); + +ecs_entity_t EcsPeriod1s = 0; +ecs_entity_t EcsPeriod1m = 0; +ecs_entity_t EcsPeriod1h = 0; +ecs_entity_t EcsPeriod1d = 0; +ecs_entity_t EcsPeriod1w = 0; + +static int32_t flecs_day_interval_count = 24; +static int32_t flecs_week_interval_count = 168; + +static +ECS_COPY(EcsPipelineStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); +}) + +static +ECS_MOVE(EcsPipelineStats, dst, src, { + ecs_os_memcpy_t(dst, src, EcsPipelineStats); + ecs_os_zeromem(src); +}) + +static +ECS_DTOR(EcsPipelineStats, ptr, { + ecs_pipeline_stats_fini(&ptr->stats); +}) + +static +void UpdateWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 1); + + const ecs_world_info_t *info = ecs_get_world_info(it->world); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + summary[i].target_fps = (double)info->target_fps; + + summary[i].frame_time_last = (double)info->frame_time_total - summary[i].frame_time_total; + summary[i].system_time_last = (double)info->system_time_total - summary[i].system_time_total; + summary[i].merge_time_last = (double)info->merge_time_total - summary[i].merge_time_total; + + summary[i].frame_time_total = (double)info->frame_time_total; + summary[i].system_time_total = (double)info->system_time_total; + summary[i].merge_time_total = (double)info->merge_time_total; + } +} + +static +void MonitorStats(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + + EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 1); + ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); + void *stats = ECS_OFFSET_T(hdr, EcsStatsHeader); + + ecs_ftime_t elapsed = hdr->elapsed; + hdr->elapsed += it->delta_time; + + int32_t t_last = (int32_t)(elapsed * 60); + int32_t t_next = (int32_t)(hdr->elapsed * 60); + int32_t i, dif = t_last - t_next; + + ecs_world_stats_t last_world = {0}; + ecs_pipeline_stats_t last_pipeline = {0}; + void *last = NULL; + + if (!dif) { + /* Copy last value so we can pass it to reduce_last */ + if (kind == ecs_id(EcsWorldStats)) { + last = &last_world; + ecs_world_stats_copy_last(&last_world, stats); + } else if (kind == ecs_id(EcsPipelineStats)) { + last = &last_pipeline; + ecs_pipeline_stats_copy_last(&last_pipeline, stats); + } + } + + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_get(world, stats); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_get(world, ecs_get_pipeline(world), stats); + } + + if (!dif) { + /* Still in same interval, combine with last measurement */ + hdr->reduce_count ++; + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce_last(stats, last, hdr->reduce_count); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce_last(stats, last, hdr->reduce_count); + } + } else if (dif > 1) { + /* More than 16ms has passed, backfill */ + for (i = 1; i < dif; i ++) { + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_repeat_last(stats); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_world_stats_repeat_last(stats); + } + } + hdr->reduce_count = 0; + } + + if (last && kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_fini(last); + } +} + +static +void ReduceStats(ecs_iter_t *it) { + void *dst = ecs_field_w_size(it, 0, 1); + void *src = ecs_field_w_size(it, 0, 2); + + ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); + + dst = ECS_OFFSET_T(dst, EcsStatsHeader); + src = ECS_OFFSET_T(src, EcsStatsHeader); + + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce(dst, src); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce(dst, src); + } +} + +static +void AggregateStats(ecs_iter_t *it) { + int32_t interval = *(int32_t*)it->ctx; + + EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1); + EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2); + + void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); + void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); + + ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); + + ecs_world_stats_t last_world = {0}; + ecs_pipeline_stats_t last_pipeline = {0}; + void *last = NULL; + + if (dst_hdr->reduce_count != 0) { + /* Copy last value so we can pass it to reduce_last */ + if (kind == ecs_id(EcsWorldStats)) { + last_world.t = 0; + ecs_world_stats_copy_last(&last_world, dst); + last = &last_world; + } else if (kind == ecs_id(EcsPipelineStats)) { + last_pipeline.t = 0; + ecs_pipeline_stats_copy_last(&last_pipeline, dst); + last = &last_pipeline; + } + } + + /* Reduce from minutes to the current day */ + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce(dst, src); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce(dst, src); + } + + if (dst_hdr->reduce_count != 0) { + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce_last(dst, last, dst_hdr->reduce_count); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce_last(dst, last, dst_hdr->reduce_count); + } + } + + /* A day has 60 24 minute intervals */ + dst_hdr->reduce_count ++; + if (dst_hdr->reduce_count >= interval) { + dst_hdr->reduce_count = 0; + } + + if (last && kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_fini(last); + } +} + +static +void flecs_stats_monitor_import( + ecs_world_t *world, + ecs_id_t kind, + size_t size) +{ + ecs_entity_t prev = ecs_set_scope(world, kind); + + // Called each frame, collects 60 measurements per second + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = MonitorStats + }); + + // Called each second, reduces into 60 measurements per minute + ecs_entity_t mw1m = ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .interval = 1.0 + }); + + // Called each minute, reduces into 60 measurements per hour + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .rate = 60, + .tick_source = mw1m + }); + + // Called each minute, reduces into 60 measurements per day + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1d), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = &flecs_day_interval_count + }); + + // Called each hour, reduces into 60 measurements per week + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }), + .query.filter.terms = {{ + .id = ecs_pair(kind, EcsPeriod1w), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = &flecs_week_interval_count + }); + + ecs_set_scope(world, prev); + + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1s), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1m), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1h), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1d), size, NULL); + ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1w), size, NULL); +} + +static +void flecs_world_monitor_import( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsWorldStats); + + flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), + sizeof(EcsWorldStats)); +} + +static +void flecs_pipeline_monitor_import( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsPipelineStats); + + ecs_set_hooks(world, EcsPipelineStats, { + .ctor = ecs_default_ctor, + .copy = ecs_copy(EcsPipelineStats), + .move = ecs_move(EcsPipelineStats), + .dtor = ecs_dtor(EcsPipelineStats) + }); + + flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats), + sizeof(EcsPipelineStats)); +} + +void FlecsMonitorImport( + ecs_world_t *world) +{ + ECS_MODULE_DEFINE(world, FlecsMonitor); + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif +#ifdef FLECS_UNITS + ECS_IMPORT(world, FlecsUnits); +#endif +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsMonitor), + "Module that automatically monitors statistics for the world & systems"); +#endif + + ecs_set_name_prefix(world, "Ecs"); + + EcsPeriod1s = ecs_new_entity(world, "EcsPeriod1s"); + EcsPeriod1m = ecs_new_entity(world, "EcsPeriod1m"); + EcsPeriod1h = ecs_new_entity(world, "EcsPeriod1h"); + EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d"); + EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w"); + + ECS_COMPONENT_DEFINE(world, EcsWorldSummary); + +#if defined(FLECS_META) && defined(FLECS_UNITS) + ecs_struct(world, { + .entity = ecs_id(EcsWorldSummary), + .members = { + { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, + { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds } + } + }); +#endif + + ecs_system(world, { + .entity = ecs_entity(world, { + .name = "UpdateWorldSummary", + .add = {ecs_dependson(EcsPreFrame)} + }), + .query.filter.terms[0] = { .id = ecs_id(EcsWorldSummary) }, + .callback = UpdateWorldSummary + }); + + ECS_SYSTEM(world, UpdateWorldSummary, EcsPreFrame, WorldSummary); + ecs_set(world, EcsWorld, EcsWorldSummary, {0}); + + flecs_world_monitor_import(world); + flecs_pipeline_monitor_import(world); + + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + ecs_measure_system_time(world, true); + } +} + +#endif + +/** + * @file addons/parser.c + * @brief Parser addon. + */ + + +#ifdef FLECS_PARSER + +#include + +#define TOK_COLON ':' +#define TOK_AND ',' +#define TOK_OR "||" +#define TOK_NOT '!' +#define TOK_OPTIONAL '?' +#define TOK_BITWISE_OR '|' +#define TOK_BRACKET_OPEN '[' +#define TOK_BRACKET_CLOSE ']' +#define TOK_SCOPE_OPEN '{' +#define TOK_SCOPE_CLOSE '}' +#define TOK_VARIABLE '$' +#define TOK_PAREN_OPEN '(' +#define TOK_PAREN_CLOSE ')' +#define TOK_EQ "==" +#define TOK_NEQ "!=" +#define TOK_MATCH "~=" +#define TOK_EXPR_STRING '"' + +#define TOK_SELF "self" +#define TOK_UP "up" +#define TOK_DOWN "down" +#define TOK_CASCADE "cascade" +#define TOK_PARENT "parent" +#define TOK_DESC "desc" + +#define TOK_OVERRIDE "OVERRIDE" +#define TOK_ROLE_AND "AND" +#define TOK_ROLE_OR "OR" +#define TOK_ROLE_NOT "NOT" +#define TOK_ROLE_TOGGLE "TOGGLE" + +#define TOK_IN "in" +#define TOK_OUT "out" +#define TOK_INOUT "inout" +#define TOK_INOUT_NONE "none" + +static +const ecs_id_t ECS_OR = (1ull << 59); + +static +const ecs_id_t ECS_NOT = (1ull << 58); + +#define ECS_MAX_TOKEN_SIZE (256) + +typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; + +const char* ecs_parse_ws_eol( + const char *ptr) +{ + while (isspace(*ptr)) { + ptr ++; + } + + return ptr; +} + +const char* ecs_parse_ws( + const char *ptr) +{ + while ((*ptr != '\n') && isspace(*ptr)) { + ptr ++; + } + + return ptr; +} + +const char* ecs_parse_digit( + const char *ptr, + char *token) +{ + char *tptr = token; + char ch = ptr[0]; + + if (!isdigit(ch) && ch != '-') { + ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); + return NULL; + } + + tptr[0] = ch; + tptr ++; + ptr ++; + + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + return ptr; +} + +/* -- Private functions -- */ + +bool flecs_isident( + char ch) +{ + return isalpha(ch) || (ch == '_'); +} + +static +bool flecs_valid_identifier_start_char( + char ch) +{ + if (ch && (flecs_isident(ch) || (ch == '*') || + (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) + { + return true; + } + + return false; +} + +static +bool flecs_valid_token_start_char( + char ch) +{ + if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') + || (ch == '[') || (ch == ']') || (ch == '`') || + flecs_valid_identifier_start_char(ch)) + { + return true; + } + + return false; +} + +static +bool flecs_valid_token_char( + char ch) +{ + if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { + return true; + } + + return false; +} + +static +bool flecs_valid_operator_char( + char ch) +{ + if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + return true; + } + + return false; +} + +const char* ecs_parse_token( + const char *name, + const char *expr, + const char *ptr, + char *token_out, + char delim) +{ + int64_t column = ptr - expr; + + ptr = ecs_parse_ws(ptr); + char *tptr = token_out, ch = ptr[0]; + + if (!flecs_valid_token_start_char(ch)) { + if (ch == '\0' || ch == '\n') { + ecs_parser_error(name, expr, column, + "unexpected end of expression"); + } else { + ecs_parser_error(name, expr, column, + "invalid start of token '%s'", ptr); + } + return NULL; + } + + tptr[0] = ch; + tptr ++; + ptr ++; + + if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { + tptr[0] = 0; + return ptr; + } + + int tmpl_nesting = 0; + bool in_str = ch == '"'; + + for (; (ch = *ptr); ptr ++) { + if (ch == '<') { + tmpl_nesting ++; + } else if (ch == '>') { + if (!tmpl_nesting) { + break; + } + tmpl_nesting --; + } else if (ch == '"') { + in_str = !in_str; + } else + if (!flecs_valid_token_char(ch) && !in_str) { + break; + } + if (delim && (ch == delim)) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + if (tmpl_nesting != 0) { + ecs_parser_error(name, expr, column, + "identifier '%s' has mismatching < > pairs", ptr); + return NULL; + } + + const char *next_ptr = ecs_parse_ws(ptr); + if (next_ptr[0] == ':' && next_ptr != ptr) { + /* Whitespace between token and : is significant */ + ptr = next_ptr - 1; + } else { + ptr = next_ptr; + } + + return ptr; +} + +const char* ecs_parse_identifier( + const char *name, + const char *expr, + const char *ptr, + char *token_out) +{ + if (!flecs_valid_identifier_start_char(ptr[0]) && (ptr[0] != '"')) { + ecs_parser_error(name, expr, (ptr - expr), + "expected start of identifier"); + return NULL; + } + + ptr = ecs_parse_token(name, expr, ptr, token_out, 0); + + return ptr; +} + +static +int flecs_parse_identifier( + const char *token, + ecs_term_id_t *out) +{ + const char *tptr = token; + if (tptr[0] == TOK_VARIABLE && tptr[1]) { + out->flags |= EcsIsVariable; + tptr ++; + } + if (tptr[0] == TOK_EXPR_STRING && tptr[1]) { + out->flags |= EcsIsName; + tptr ++; + if (tptr[0] == TOK_NOT) { + /* Already parsed */ + tptr ++; + } + } + + char *name = ecs_os_strdup(tptr); + out->name = name; + + ecs_size_t len = ecs_os_strlen(name); + if (out->flags & EcsIsName) { + if (name[len - 1] != TOK_EXPR_STRING) { + ecs_parser_error(NULL, token, 0, "missing '\"' at end of string"); + return -1; + } else { + name[len - 1] = '\0'; + } + } + + return 0; +} + +static +ecs_entity_t flecs_parse_role( + const char *name, + const char *sig, + int64_t column, + const char *token) +{ + 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_NOT)) { + return ECS_NOT; + } else if (!ecs_os_strcmp(token, TOK_OVERRIDE)) { + return ECS_OVERRIDE; + } else if (!ecs_os_strcmp(token, TOK_ROLE_TOGGLE)) { + return ECS_TOGGLE; + } else { + ecs_parser_error(name, sig, column, "invalid role '%s'", token); + return 0; + } +} + +static +ecs_oper_kind_t flecs_parse_operator( + char ch) +{ + if (ch == TOK_OPTIONAL) { + return EcsOptional; + } else if (ch == TOK_NOT) { + return EcsNot; + } else { + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return 0; +} + +static +const char* flecs_parse_annotation( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + ecs_inout_kind_t *inout_kind_out) +{ + char token[ECS_MAX_TOKEN_SIZE]; + + ptr = ecs_parse_identifier(name, sig, ptr, token); + if (!ptr) { + return NULL; + } + + if (!ecs_os_strcmp(token, TOK_IN)) { + *inout_kind_out = EcsIn; + } else + if (!ecs_os_strcmp(token, TOK_OUT)) { + *inout_kind_out = EcsOut; + } else + if (!ecs_os_strcmp(token, TOK_INOUT)) { + *inout_kind_out = EcsInOut; + } else if (!ecs_os_strcmp(token, TOK_INOUT_NONE)) { + *inout_kind_out = EcsInOutNone; + } + + ptr = ecs_parse_ws(ptr); + + if (ptr[0] != TOK_BRACKET_CLOSE) { + ecs_parser_error(name, sig, column, "expected ]"); + return NULL; + } + + return ptr + 1; +} + +static +uint8_t flecs_parse_set_token( + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_SELF)) { + return EcsSelf; + } else if (!ecs_os_strcmp(token, TOK_UP)) { + return EcsUp; + } else if (!ecs_os_strcmp(token, TOK_DOWN)) { + return EcsDown; + } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { + return EcsCascade; + } else if (!ecs_os_strcmp(token, TOK_DESC)) { + return EcsDesc; + } else if (!ecs_os_strcmp(token, TOK_PARENT)) { + return EcsParent; + } else { + return 0; + } +} + +static +const char* flecs_parse_term_flags( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_id_t *id, + char tok_end) +{ + char token_buf[ECS_MAX_TOKEN_SIZE] = {0}; + if (!token) { + token = token_buf; + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } + } + + do { + uint8_t tok = flecs_parse_set_token(token); + if (!tok) { + ecs_parser_error(name, expr, column, + "invalid set token '%s'", token); + return NULL; + } + + if (id->flags & tok) { + ecs_parser_error(name, expr, column, + "duplicate set token '%s'", token); + return NULL; + } + + id->flags |= tok; + + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + + /* Relationship (overrides IsA default) */ + if (!isdigit(ptr[0]) && flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } + + id->trav = ecs_lookup_fullpath(world, token); + if (!id->trav) { + ecs_parser_error(name, expr, column, + "unresolved identifier '%s'", token); + return NULL; + } + + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_ws(ptr + 1); + } else if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, + "expected ',' or ')'"); + return NULL; + } + } + + if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, "expected ')', got '%c'", + ptr[0]); + return NULL; + } else { + ptr = ecs_parse_ws(ptr + 1); + if (ptr[0] != tok_end && ptr[0] != TOK_AND && ptr[0] != 0) { + ecs_parser_error(name, expr, column, + "expected end of set expr"); + return NULL; + } + } + } + + /* Next token in set expression */ + if (ptr[0] == TOK_BITWISE_OR) { + ptr ++; + if (flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } + } + + /* End of set expression */ + } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { + break; + } + } while (true); + + return ptr; +} + +static +const char* flecs_parse_arguments( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_t *term, + ecs_oper_kind_t *extra_oper, + ecs_term_id_t *extra_args) +{ + (void)column; + + int32_t i, arg = 0; + + if (extra_args) { + ecs_os_memset_n(extra_args, 0, ecs_term_id_t, ECS_PARSER_MAX_ARGS); + } + + if (!term) { + arg = 2; + } + + do { + if (flecs_valid_token_start_char(ptr[0])) { + if ((arg == ECS_PARSER_MAX_ARGS) || (!extra_args && arg == 2)) { + ecs_parser_error(name, expr, (ptr - expr), + "too many arguments in term"); + goto error; + } + + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + ptr = ecs_parse_ws_eol(ptr); + + ecs_term_id_t *term_id = NULL; + if (arg == 0) { + term_id = &term->src; + } else if (arg == 1) { + term_id = &term->second; + } else { + term_id = &extra_args[arg - 2]; + } + + /* If token is a colon, the token is an identifier followed by a + * set expression. */ + if (ptr[0] == TOK_COLON) { + if (flecs_parse_identifier(token, term_id)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + ptr = ecs_parse_ws_eol(ptr + 1); + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, + NULL, term_id, TOK_PAREN_CLOSE); + if (!ptr) { + goto error; + } + + /* Check for term flags */ + } else if (!ecs_os_strcmp(token, TOK_CASCADE) || + !ecs_os_strcmp(token, TOK_DESC) || + !ecs_os_strcmp(token, TOK_SELF) || + !ecs_os_strcmp(token, TOK_UP) || + !ecs_os_strcmp(token, TOK_DOWN) || + !(ecs_os_strcmp(token, TOK_PARENT))) + { + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, + token, term_id, TOK_PAREN_CLOSE); + if (!ptr) { + goto error; + } + + /* Regular identifier */ + } else if (flecs_parse_identifier(token, term_id)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + if (ptr[0] == TOK_AND) { + if (extra_oper && *extra_oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot mix ',' and '||' in term arguments"); + goto error; + } + ptr = ecs_parse_ws_eol(ptr + 1); + + if (term) { + term->id_flags = ECS_PAIR; + } + + } else if (ptr[0] == TOK_PAREN_CLOSE) { + ptr = ecs_parse_ws(ptr + 1); + break; + + } else if (extra_oper && ptr[0] == TOK_OR[0] && ptr[1] == TOK_OR[1]){ + if (arg >= 2 && *extra_oper != EcsOr) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot mix ',' and '||' in term arguments"); + goto error; + } + + *extra_oper = EcsOr; + ptr = ecs_parse_ws_eol(ptr + 2); + + if (term) { + term->id_flags = ECS_PAIR; + } + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected ',' or ')'"); + goto error; + } + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier or set expression"); + goto error; + } + + arg ++; + + } while (true); + + return ptr; +error: + if (term && term->src.name) { + ecs_os_free(ECS_CONST_CAST(char*, term->src.name)); + term->src.name = NULL; + } + if (term && term->second.name) { + ecs_os_free(ECS_CONST_CAST(char*, term->second.name)); + term->second.name = NULL; + } + if (extra_args) { + for (i = 2; i < arg + 1; i ++) { + if (extra_args[i - 2].name) { + ecs_os_free(ECS_CONST_CAST(char*, extra_args[i - 2].name)); + extra_args[i - 2].name = NULL; + } + } + } + return NULL; +} + +static +void flecs_parser_unexpected_char( + const char *name, + const char *expr, + const char *ptr, + char ch) +{ + if (ch && (ch != '\n')) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ch); + } else { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected end of term"); + } +} + +static +const char* flecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term_out, + ecs_oper_kind_t *extra_oper, + ecs_term_id_t *extra_args) +{ + const char *ptr = expr; + char token[ECS_MAX_TOKEN_SIZE] = {0}; + ecs_term_t term = { .move = true /* parser never owns resources */ }; + + ptr = ecs_parse_ws(ptr); + + /* Inout specifiers always come first */ + if (ptr[0] == TOK_BRACKET_OPEN) { + ptr = flecs_parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); + if (!ptr) { + goto error; + } + ptr = ecs_parse_ws_eol(ptr); + } + + if (flecs_valid_operator_char(ptr[0])) { + term.oper = flecs_parse_operator(ptr[0]); + ptr = ecs_parse_ws(ptr + 1); + } + + /* If next token is the start of an identifier, it could be either a type + * role, source or component identifier */ + if (flecs_valid_identifier_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + /* Is token a type role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr ++; + goto flecs_parse_role; + } + + /* Is token a predicate? */ + if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_predicate; + } + + /* Next token must be a predicate */ + goto parse_predicate; + + /* Pair with implicit subject */ + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + + /* Open query scope */ + } else if (ptr[0] == TOK_SCOPE_OPEN) { + term.first.id = EcsScopeOpen; + term.src.id = 0; + term.src.flags = EcsIsEntity; + term.inout = EcsInOutNone; + goto parse_done; + + /* Close query scope */ + } else if (ptr[0] == TOK_SCOPE_CLOSE) { + term.first.id = EcsScopeClose; + term.src.id = 0; + term.src.flags = EcsIsEntity; + term.inout = EcsInOutNone; + ptr = ecs_parse_ws(ptr + 1); + goto parse_done; + + /* Nothing else expected here */ + } else { + flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; + } + +flecs_parse_role: + term.id_flags = flecs_parse_role(name, expr, (ptr - expr), token); + if (!term.id_flags) { + goto error; + } + + ptr = ecs_parse_ws(ptr); + + /* If next token is the source token, this is an empty source */ + if (flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + /* If not, it's a predicate */ + goto parse_predicate; + + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after role"); + goto error; + } + +parse_predicate: + if (flecs_parse_identifier(token, &term.first)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + /* Set expression */ + if (ptr[0] == TOK_COLON) { + ptr = ecs_parse_ws(ptr + 1); + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, + &term.first, TOK_COLON); + if (!ptr) { + goto error; + } + + ptr = ecs_parse_ws(ptr); + + if (ptr[0] == TOK_AND || !ptr[0]) { + goto parse_done; + } + + if (ptr[0] != TOK_COLON) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected token '%c' after predicate set expression", ptr[0]); + goto error; + } + + ptr = ecs_parse_ws(ptr + 1); + } else if (!ecs_os_strncmp(ptr, TOK_EQ, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_eq; + } else if (!ecs_os_strncmp(ptr, TOK_NEQ, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_neq; + } else if (!ecs_os_strncmp(ptr, TOK_MATCH, 2)) { + ptr = ecs_parse_ws(ptr + 2); + goto parse_match; + } else { + ptr = ecs_parse_ws(ptr); + } + + if (ptr[0] == TOK_PAREN_OPEN) { + ptr = ecs_parse_ws_eol(ptr + 1); + if (ptr[0] == TOK_PAREN_CLOSE) { + term.src.flags = EcsIsEntity; + term.src.id = 0; + ptr ++; + ptr = ecs_parse_ws(ptr); + } else { + ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, + token, &term, extra_oper, extra_args); + } + + goto parse_done; + } + + goto parse_done; + +parse_eq: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredEq; + goto parse_right_operand; + +parse_neq: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredEq; + if (term.oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator combination"); + goto error; + } + term.oper = EcsNot; + goto parse_right_operand; + +parse_match: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredMatch; + goto parse_right_operand; + +parse_right_operand: + if (flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + if (term.first.id == EcsPredMatch) { + if (token[0] == '"' && token[1] == '!') { + term.oper = EcsNot; + } + } + + if (flecs_parse_identifier(token, &term.second)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + term.src.flags &= ~EcsTraverseFlags; + term.src.flags |= EcsSelf; + term.inout = EcsInOutNone; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier"); + goto error; + } + goto parse_done; +parse_pair: + ptr = ecs_parse_identifier(name, expr, ptr + 1, token); + if (!ptr) { + goto error; + } + + if (ptr[0] == TOK_COLON) { + ptr = ecs_parse_ws(ptr + 1); + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, + NULL, &term.first, TOK_PAREN_CLOSE); + if (!ptr) { + goto error; + } + } + + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_ws(ptr + 1); + if (ptr[0] == TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier for second element of pair"); + goto error; + } + + term.src.id = EcsThis; + term.src.flags |= EcsIsVariable; + goto parse_pair_predicate; + } else if (ptr[0] == TOK_PAREN_CLOSE) { + term.src.id = EcsThis; + term.src.flags |= EcsIsVariable; + goto parse_pair_predicate; + } else { + flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; + } + +parse_pair_predicate: + if (flecs_parse_identifier(token, &term.first)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + ptr = ecs_parse_ws(ptr); + if (flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + if (ptr[0] == TOK_COLON) { + ptr = ecs_parse_ws(ptr + 1); + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, + NULL, &term.second, TOK_PAREN_CLOSE); + if (!ptr) { + goto error; + } + } + + if (ptr[0] == TOK_PAREN_CLOSE || ptr[0] == TOK_AND || ptr[0] == TOK_OR[0]) { + goto parse_pair_object; + } else { + flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; + } + } else if (ptr[0] == TOK_PAREN_CLOSE) { + /* No object */ + ptr ++; + goto parse_done; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected pair object or ')'"); + goto error; + } + +parse_pair_object: + if (flecs_parse_identifier(token, &term.second)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } + + if (term.id_flags == 0) { + term.id_flags = ECS_PAIR; + } + + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_ws(ptr + 1); + ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, token, + NULL, extra_oper, extra_args); + if (!ptr) { + goto error; + } + } else if (extra_oper && ptr[0] == TOK_OR[0] && ptr[1] == TOK_OR[1]) { + ptr = ecs_parse_ws_eol(ptr + 2); + *extra_oper = EcsOr; + ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, token, + NULL, extra_oper, extra_args); + if (!ptr) { + goto error; + } + } else { + ptr ++; + } + + ptr = ecs_parse_ws(ptr); + goto parse_done; + +parse_done: + *term_out = term; + return ptr; + +error: + ecs_term_fini(&term); + *term_out = (ecs_term_t){0}; + return NULL; +} + +static +bool flecs_is_valid_end_of_term( + const char *ptr) +{ + if ((ptr[0] == TOK_AND) || /* another term with And operator */ + (ptr[0] == TOK_OR[0]) || /* another term with Or operator */ + (ptr[0] == '\n') || /* newlines are valid */ + (ptr[0] == '\0') || /* end of string */ + (ptr[0] == '/') || /* comment (in plecs) */ + (ptr[0] == '{') || /* scope (in plecs) */ + (ptr[0] == '}') || + (ptr[0] == '[') || /* collection scope (in plecs) */ + (ptr[0] == ']') || + (ptr[0] == ':') || /* inheritance (in plecs) */ + (ptr[0] == '=')) /* assignment (in plecs) */ + { + return true; + } + return false; +} + +char* ecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term, + ecs_oper_kind_t *extra_oper, + ecs_term_id_t *extra_args, + bool allow_newline) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + + if (extra_oper) { + *extra_oper = EcsAnd; + } + + ecs_term_id_t *src = &term->src; + + if (ptr != expr) { + if (ptr[0]) { + if (ptr[0] == ',') { + ptr ++; + } else if (ptr[0] == '|') { + ptr += 2; + } else if (ptr[0] == '{') { + ptr ++; + } else if (ptr[0] == '}') { + /* nothing to be done */ + } else { + ecs_parser_error(name, expr, (ptr - expr), + "invalid preceding token"); + } + } + } + + ptr = ecs_parse_ws_eol(ptr); + if (!ptr[0]) { + *term = (ecs_term_t){0}; + return ECS_CONST_CAST(char*, ptr); + } + + if (ptr == expr && !strcmp(expr, "0")) { + return ECS_CONST_CAST(char*, &ptr[1]); + } + + /* Parse next element */ + ptr = flecs_parse_term(world, name, ptr, term, extra_oper, extra_args); + if (!ptr) { + goto error; + } + + /* Check for $() notation */ + if (term->first.name && !ecs_os_strcmp(term->first.name, "$")) { + if (term->src.name) { + /* Safe, parser owns name */ + ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); + + term->first = term->src; + + if (term->second.name) { + term->src = term->second; + } else { + term->src.id = EcsThis; + term->src.name = NULL; + term->src.flags |= EcsIsVariable; + } + + const char *var_name = strrchr(term->first.name, '.'); + if (var_name) { + var_name ++; + } else { + var_name = term->first.name; + } + + term->second.name = ecs_os_strdup(var_name); + term->second.flags |= EcsIsVariable; + } + } + + /* Post-parse consistency checks */ + + /* If next token is OR, term is part of an OR expression */ + if (!ecs_os_strncmp(ptr, TOK_OR, 2)) { + /* An OR operator must always follow an AND or another OR */ + if (term->oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot combine || with other operators"); + goto error; + } + + term->oper = EcsOr; + } + + /* Term must either end in end of expression, AND or OR token */ + if (!flecs_is_valid_end_of_term(ptr)) { + if (!flecs_isident(ptr[0]) || ((ptr != expr) && (ptr[-1] != ' '))) { + ecs_parser_error(name, expr, (ptr - expr), + "expected end of expression or next term"); + goto error; + } + } + + /* If the term just contained a 0, the expression has nothing. Ensure + * that after the 0 nothing else follows */ + if (term->first.name && !ecs_os_strcmp(term->first.name, "0")) { + if (ptr[0]) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected term after 0"); + goto error; + } + + if (src->flags != 0) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid combination of 0 with non-default subject"); + goto error; + } + + src->flags = EcsIsEntity; + src->id = 0; + /* Safe, parser owns string */ + ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); + term->first.name = NULL; + } + + /* Cannot combine EcsIsEntity/0 with operators other than AND */ + if (term->oper != EcsAnd && ecs_term_match_0(term)) { + if (term->first.id != EcsScopeOpen && term->first.id != EcsScopeClose) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator for empty source"); + goto error; + } + } + + /* Automatically assign This if entity is not assigned and the set is + * nothing */ + if (!(src->flags & EcsIsEntity)) { + if (!src->name) { + if (!src->id) { + src->id = EcsThis; + src->flags |= EcsIsVariable; + } + } + } + + if (src->name && !ecs_os_strcmp(src->name, "0")) { + src->id = 0; + src->flags = EcsIsEntity; + } + + /* Process role */ + if (term->id_flags == ECS_AND) { + term->oper = EcsAndFrom; + term->id_flags = 0; + } else if (term->id_flags == ECS_OR) { + term->oper = EcsOrFrom; + term->id_flags = 0; + } else if (term->id_flags == ECS_NOT) { + term->oper = EcsNotFrom; + term->id_flags = 0; + } + + if (allow_newline) { + ptr = ecs_parse_ws_eol(ptr); + } else { + ptr = ecs_parse_ws(ptr); + } + + return ECS_CONST_CAST(char*, ptr); +error: + if (term) { + ecs_term_fini(term); + } + return NULL; +} + +#endif + +/** + * @file addons/plecs.c + * @brief Plecs addon. + */ + + +#ifdef FLECS_PLECS + +ECS_COMPONENT_DECLARE(EcsScript); + +#include + +#define TOK_NEWLINE '\n' +#define TOK_USING "using" +#define TOK_MODULE "module" +#define TOK_WITH "with" +#define TOK_CONST "const" +#define TOK_PROP "prop" +#define TOK_ASSEMBLY "assembly" + +#define STACK_MAX_SIZE (64) + +typedef struct { + ecs_value_t value; + bool owned; +} plecs_with_value_t; + +typedef struct { + const char *name; + const char *code; + + ecs_entity_t last_predicate; + ecs_entity_t last_subject; + ecs_entity_t last_object; + + ecs_id_t last_assign_id; + ecs_entity_t assign_to; + + ecs_entity_t scope[STACK_MAX_SIZE]; + ecs_entity_t default_scope_type[STACK_MAX_SIZE]; + ecs_entity_t with[STACK_MAX_SIZE]; + ecs_entity_t using[STACK_MAX_SIZE]; + int32_t with_frames[STACK_MAX_SIZE]; + plecs_with_value_t with_value_frames[STACK_MAX_SIZE]; + int32_t using_frames[STACK_MAX_SIZE]; + int32_t sp; + int32_t with_frame; + int32_t using_frame; + ecs_entity_t global_with; + ecs_entity_t assembly; + const char *assembly_start, *assembly_stop; + + char *annot[STACK_MAX_SIZE]; + int32_t annot_count; + + ecs_vars_t vars; + char var_name[256]; + ecs_entity_t var_type; + + bool with_stmt; + bool scope_assign_stmt; + bool assign_stmt; + bool assembly_stmt; + bool assembly_instance; + bool isa_stmt; + bool decl_stmt; + bool decl_type; + bool var_stmt; + bool var_is_prop; + bool is_module; + + int32_t errors; +} plecs_state_t; + +static +int flecs_plecs_parse( + ecs_world_t *world, + const char *name, + const char *expr, + ecs_vars_t *vars, + ecs_entity_t script, + ecs_entity_t instance); + +static void flecs_dtor_script(EcsScript *ptr) { + ecs_os_free(ptr->script); + ecs_vec_fini_t(NULL, &ptr->using_, ecs_entity_t); + + int i, count = ptr->prop_defaults.count; + ecs_value_t *values = ptr->prop_defaults.array; + for (i = 0; i < count; i ++) { + ecs_value_free(ptr->world, values[i].type, values[i].ptr); + } + + ecs_vec_fini_t(NULL, &ptr->prop_defaults, ecs_value_t); +} + +static +ECS_MOVE(EcsScript, dst, src, { + flecs_dtor_script(dst); + dst->using_ = src->using_; + dst->prop_defaults = src->prop_defaults; + dst->script = src->script; + dst->world = src->world; + ecs_os_zeromem(&src->using_); + ecs_os_zeromem(&src->prop_defaults); + src->script = NULL; + src->world = NULL; +}) + +static +ECS_DTOR(EcsScript, ptr, { + flecs_dtor_script(ptr); +}) + +/* Assembly ctor to initialize with default property values */ +static +void flecs_assembly_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ti) +{ + ecs_world_t *world = ti->hooks.ctx; + ecs_entity_t assembly = ti->component; + const EcsStruct *st = ecs_get(world, assembly, EcsStruct); + + if (!st) { + ecs_err("assembly '%s' is not a struct, cannot construct", ti->name); + return; + } + + const EcsScript *script = ecs_get(world, assembly, EcsScript); + if (!script) { + ecs_err("assembly '%s' is not a script, cannot construct", ti->name); + return; + } + + if (st->members.count != script->prop_defaults.count) { + ecs_err("number of props (%d) of assembly '%s' does not match members" + " (%d), cannot construct", script->prop_defaults.count, + ti->name, st->members.count); + return; + } + + const ecs_member_t *members = st->members.array; + int32_t i, m, member_count = st->members.count; + ecs_value_t *values = script->prop_defaults.array; + for (m = 0; m < member_count; m ++) { + const ecs_member_t *member = &members[m]; + ecs_value_t *value = &values[m]; + const ecs_type_info_t *mti = ecs_get_type_info(world, member->type); + if (!mti) { + ecs_err("failed to get type info for prop '%s' of assembly '%s'", + member->name, ti->name); + return; + } + + for (i = 0; i < count; i ++) { + void *el = ECS_ELEM(ptr, ti->size, i); + ecs_value_copy_w_type_info(world, mti, + ECS_OFFSET(el, member->offset), value->ptr); + } + } +} + +/* Assembly on_set handler to update contents for new property values */ +static +void flecs_assembly_on_set( + ecs_iter_t *it) +{ + if (it->table->flags & EcsTableIsPrefab) { + /* Don't instantiate assemblies for prefabs */ + return; + } + + ecs_world_t *world = it->world; + ecs_entity_t assembly = ecs_field_id(it, 1); + const char *name = ecs_get_name(world, assembly); + ecs_record_t *r = ecs_record_find(world, assembly); + + const EcsComponent *ct = ecs_record_get(world, r, EcsComponent); + ecs_get(world, assembly, EcsComponent); + if (!ct) { + ecs_err("assembly '%s' is not a component", name); + return; + } + + const EcsStruct *st = ecs_record_get(world, r, EcsStruct); + if (!st) { + ecs_err("assembly '%s' is not a struct", name); + return; + } + + const EcsScript *script = ecs_record_get(world, r, EcsScript); + if (!script) { + ecs_err("assembly '%s' is missing a script", name); + return; + } + + void *data = ecs_field_w_size(it, flecs_ito(size_t, ct->size), 1); + + int32_t i, m; + for (i = 0; i < it->count; i ++) { + /* Create variables to hold assembly properties */ + ecs_vars_t vars = {0}; + ecs_vars_init(world, &vars); + + /* Populate properties from assembly members */ + const ecs_member_t *members = st->members.array; + for (m = 0; m < st->members.count; m ++) { + const ecs_member_t *member = &members[m]; + + ecs_value_t v = {0}; /* Prevent allocating value */ + ecs_expr_var_t *var = ecs_vars_declare_w_value( + &vars, member->name, &v); + if (var == NULL) { + ecs_err("could not create prop '%s' for assembly '%s'", + member->name, name); + break; + } + + /* Assign assembly property from assembly instance */ + var->value.type = member->type; + var->value.ptr = ECS_OFFSET(data, member->offset); + var->owned = false; + } + + /* Populate $this variable with instance entity */ + ecs_entity_t instance = it->entities[i]; + ecs_value_t v = {0}; + ecs_expr_var_t *var = ecs_vars_declare_w_value( + &vars, "this", &v); + var->value.type = ecs_id(ecs_entity_t); + var->value.ptr = &instance; + var->owned = false; + + /* Update script with new code/properties */ + ecs_script_update(world, assembly, instance, script->script, &vars); + ecs_vars_fini(&vars); + + if (ecs_record_has_id(world, r, EcsFlatten)) { + ecs_flatten(it->real_world, ecs_childof(instance), NULL); + } + + data = ECS_OFFSET(data, ct->size); + } +} + +/* Delete contents of assembly instance */ +static +void flecs_assembly_on_remove( + ecs_iter_t *it) +{ + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t instance = it->entities[i]; + ecs_script_clear(it->world, 0, instance); + } +} + +/* Set default property values on assembly Script component */ +static +int flecs_assembly_init_defaults( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_entity_t assembly, + EcsScript *script, + plecs_state_t *state) +{ + const EcsStruct *st = ecs_get(world, assembly, EcsStruct); + int32_t i, count = st->members.count; + const ecs_member_t *members = st->members.array; + + ecs_vec_init_t(NULL, &script->prop_defaults, ecs_value_t, count); + + for (i = 0; i < count; i ++) { + const ecs_member_t *member = &members[i]; + ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, member->name); + if (!var) { + char *assembly_name = ecs_get_fullpath(world, assembly); + ecs_parser_error(name, expr, ptr - expr, + "missing property '%s' for assembly '%s'", + member->name, assembly_name); + ecs_os_free(assembly_name); + return -1; + } + + if (member->type != var->value.type) { + char *assembly_name = ecs_get_fullpath(world, assembly); + ecs_parser_error(name, expr, ptr - expr, + "property '%s' for assembly '%s' has mismatching type", + member->name, assembly_name); + ecs_os_free(assembly_name); + return -1; + } + + ecs_value_t *pv = ecs_vec_append_t(NULL, + &script->prop_defaults, ecs_value_t); + pv->type = member->type; + pv->ptr = var->value.ptr; + var->owned = false; /* Transfer ownership */ + } + + return 0; +} + +/* Create new assembly */ +static +int flecs_assembly_create( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_entity_t assembly, + char *script_code, + plecs_state_t *state) +{ + const EcsStruct *st = ecs_get(world, assembly, EcsStruct); + if (!st || !st->members.count) { + char *assembly_name = ecs_get_fullpath(world, assembly); + ecs_parser_error(name, expr, ptr - expr, + "assembly '%s' has no properties", assembly_name); + ecs_os_free(assembly_name); + ecs_os_free(script_code); + return -1; + } + + ecs_add_id(world, assembly, EcsAlwaysOverride); + + EcsScript *script = ecs_get_mut(world, assembly, EcsScript); + flecs_dtor_script(script); + script->world = world; + script->script = script_code; + ecs_vec_reset_t(NULL, &script->using_, ecs_entity_t); + + ecs_entity_t scope = ecs_get_scope(world); + if (scope && (scope = ecs_get_target(world, scope, EcsChildOf, 0))) { + ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = scope; + } + + int i, count = state->using_frame; + for (i = 0; i < count; i ++) { + ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = + state->using[i]; + } + + if (flecs_assembly_init_defaults( + world, name, expr, ptr, assembly, script, state)) + { + return -1; + } + + ecs_modified(world, assembly, EcsScript); + + ecs_set_hooks_id(world, assembly, &(ecs_type_hooks_t) { + .ctor = flecs_assembly_ctor, + .on_set = flecs_assembly_on_set, + .on_remove = flecs_assembly_on_remove, + .ctx = world + }); + + return 0; +} + +/* Parser */ + +static +bool plecs_is_newline_comment( + const char *ptr) +{ + if (ptr[0] == '/' && ptr[1] == '/') { + return true; + } + return false; +} + +static +const char* plecs_parse_fluff( + const char *ptr) +{ + do { + /* Skip whitespaces before checking for a comment */ + ptr = ecs_parse_ws(ptr); + + /* Newline comment, skip until newline character */ + if (plecs_is_newline_comment(ptr)) { + ptr += 2; + + while (ptr[0] && ptr[0] != TOK_NEWLINE) { + ptr ++; + } + } + + /* If a newline character is found, skip it */ + if (ptr[0] == TOK_NEWLINE) { + ptr ++; + } + + } while (isspace(ptr[0]) || plecs_is_newline_comment(ptr)); + + return ptr; +} + +static +ecs_entity_t plecs_lookup( + const ecs_world_t *world, + const char *path, + plecs_state_t *state, + ecs_entity_t rel, + bool is_subject) +{ + ecs_entity_t e = 0; + + if (!is_subject) { + ecs_entity_t oneof = 0; + if (rel) { + if (ecs_has_id(world, rel, EcsOneOf)) { + oneof = rel; + } else { + oneof = ecs_get_target(world, rel, EcsOneOf, 0); + } + if (oneof) { + return ecs_lookup_path_w_sep( + world, oneof, path, NULL, NULL, false); + } + } + int using_scope = state->using_frame - 1; + for (; using_scope >= 0; using_scope--) { + e = ecs_lookup_path_w_sep( + world, state->using[using_scope], path, NULL, NULL, false); + if (e) { + break; + } + } + } + + if (!e) { + e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); + } + + return e; +} + +/* Lookup action used for deserializing entity refs in component values */ +static +ecs_entity_t plecs_lookup_action( + const ecs_world_t *world, + const char *path, + void *ctx) +{ + return plecs_lookup(world, path, ctx, 0, false); +} + +static +void plecs_apply_with_frame( + ecs_world_t *world, + plecs_state_t *state, + ecs_entity_t e) +{ + int32_t i, frame_count = state->with_frames[state->sp]; + for (i = 0; i < frame_count; i ++) { + ecs_id_t id = state->with[i]; + plecs_with_value_t *v = &state->with_value_frames[i]; + if (v->value.type) { + void *ptr = ecs_get_mut_id(world, e, id); + ecs_value_copy(world, v->value.type, ptr, v->value.ptr); + ecs_modified_id(world, e, id); + } else { + ecs_add_id(world, e, id); + } + } +} + +static +ecs_entity_t plecs_ensure_entity( + ecs_world_t *world, + plecs_state_t *state, + const char *path, + ecs_entity_t rel, + bool is_subject) +{ + if (!path) { + return 0; + } + + ecs_entity_t e = 0; + bool is_anonymous = !ecs_os_strcmp(path, "_"); + bool is_new = false; + if (is_anonymous) { + path = NULL; + e = ecs_new_id(world); + is_new = true; + } + + if (!e) { + e = plecs_lookup(world, path, state, rel, is_subject); + } + + if (!e) { + is_new = true; + if (rel && flecs_get_oneof(world, rel)) { + /* If relationship has oneof and entity was not found, don't proceed + * with creating an entity as this can cause asserts later on */ + char *relstr = ecs_get_fullpath(world, rel); + ecs_parser_error(state->name, 0, 0, + "invalid identifier '%s' for relationship '%s'", path, relstr); + ecs_os_free(relstr); + return 0; + } + + ecs_entity_t prev_scope = 0; + ecs_entity_t prev_with = 0; + if (!is_subject) { + /* Don't apply scope/with for non-subject entities */ + prev_scope = ecs_set_scope(world, 0); + prev_with = ecs_set_with(world, 0); + } + + e = ecs_add_path(world, e, 0, path); + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + + if (prev_scope) { + ecs_set_scope(world, prev_scope); + } + if (prev_with) { + ecs_set_with(world, prev_with); + } + } else { + /* If entity exists, make sure it gets the right scope and with */ + if (is_subject) { + ecs_entity_t scope = ecs_get_scope(world); + if (scope) { + ecs_add_pair(world, e, EcsChildOf, scope); + } + + ecs_entity_t with = ecs_get_with(world); + if (with) { + ecs_add_id(world, e, with); + } + } + } + + if (is_new) { + if (state->assembly && !state->assembly_instance) { + ecs_add_id(world, e, EcsPrefab); + } + + if (state->global_with) { + ecs_add_id(world, e, state->global_with); + } + } + + return e; +} + +static +ecs_entity_t plecs_ensure_term_id( + ecs_world_t *world, + plecs_state_t *state, + ecs_term_id_t *term_id, + const char *expr, + int64_t column, + ecs_entity_t pred, + bool is_subject) +{ + ecs_entity_t result = 0; + const char *name = term_id->name; + if (term_id->flags & EcsIsVariable) { + if (name != NULL) { + ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, name); + if (!var) { + ecs_parser_error(name, expr, column, + "unresolved variable '%s'", name); + return 0; + } + if (var->value.type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, column, + "variable '%s' is not an entity", name); + return 0; + } + result = *(ecs_entity_t*)var->value.ptr; + if (!result) { + ecs_parser_error(name, expr, column, + "variable '%s' is not initialized with valid entity", name); + return 0; + } + } else if (term_id->id) { + result = term_id->id; + } else { + ecs_parser_error(name, expr, column, "invalid variable in term"); + return 0; + } + } else { + result = plecs_ensure_entity(world, state, name, pred, is_subject); + } + return result; +} + +static +bool plecs_pred_is_subj( + ecs_term_t *term, + plecs_state_t *state) +{ + if (term->src.name != NULL) { + return false; + } + if (term->second.name != NULL) { + return false; + } + if (ecs_term_match_0(term)) { + return false; + } + if (state->with_stmt) { + return false; + } + if (state->assign_stmt) { + return false; + } + if (state->isa_stmt) { + return false; + } + if (state->decl_type) { + return false; + } + + return true; +} + +/* Set masks aren't useful in plecs, so translate them back to entity names */ +static +const char* plecs_set_mask_to_name( + ecs_flags32_t flags) +{ + flags &= EcsTraverseFlags; + if (flags == EcsSelf) { + return "self"; + } else if (flags == EcsUp) { + return "up"; + } else if (flags == EcsDown) { + return "down"; + } else if (flags == EcsCascade || flags == (EcsUp|EcsCascade)) { + return "cascade"; + } else if (flags == EcsParent) { + return "parent"; + } + return NULL; +} + +static +char* plecs_trim_annot( + char *annot) +{ + annot = ECS_CONST_CAST(char*, ecs_parse_ws(annot)); + int32_t len = ecs_os_strlen(annot) - 1; + while (isspace(annot[len]) && (len > 0)) { + annot[len] = '\0'; + len --; + } + return annot; +} + +static +void plecs_apply_annotations( + ecs_world_t *world, + ecs_entity_t subj, + plecs_state_t *state) +{ + (void)world; + (void)subj; + (void)state; +#ifdef FLECS_DOC + int32_t i = 0, count = state->annot_count; + for (i = 0; i < count; i ++) { + char *annot = state->annot[i]; + if (!ecs_os_strncmp(annot, "@brief ", 7)) { + annot = plecs_trim_annot(annot + 7); + ecs_doc_set_brief(world, subj, annot); + } else if (!ecs_os_strncmp(annot, "@link ", 6)) { + annot = plecs_trim_annot(annot + 6); + ecs_doc_set_link(world, subj, annot); + } else if (!ecs_os_strncmp(annot, "@name ", 6)) { + annot = plecs_trim_annot(annot + 6); + ecs_doc_set_name(world, subj, annot); + } else if (!ecs_os_strncmp(annot, "@color ", 7)) { + annot = plecs_trim_annot(annot + 7); + ecs_doc_set_color(world, subj, annot); + } + } +#else + ecs_warn("cannot apply annotations, doc addon is missing"); +#endif +} + +static +int plecs_create_term( + ecs_world_t *world, + ecs_term_t *term, + const char *name, + const char *expr, + int64_t column, + plecs_state_t *state) +{ + state->last_subject = 0; + state->last_predicate = 0; + state->last_object = 0; + state->last_assign_id = 0; + + const char *subj_name = term->src.name; + if (!subj_name) { + subj_name = plecs_set_mask_to_name(term->src.flags); + } + + if (!ecs_term_id_is_set(&term->first)) { + ecs_parser_error(name, expr, column, "missing term in expression"); + return -1; + } + + if (state->assign_stmt && !ecs_term_match_this(term)) { + ecs_parser_error(name, expr, column, + "invalid statement in assign statement"); + return -1; + } + + bool pred_as_subj = plecs_pred_is_subj(term, state); + ecs_entity_t subj = 0, obj = 0, pred = plecs_ensure_term_id( + world, state, &term->first, expr, column, 0, pred_as_subj); + if (!pred) { + return -1; + } + + subj = plecs_ensure_entity(world, state, subj_name, pred, true); + + if (ecs_term_id_is_set(&term->second)) { + obj = plecs_ensure_term_id(world, state, &term->second, expr, column, + pred, !state->assign_stmt && !state->with_stmt); + if (!obj) { + return -1; + } + } + + if (state->assign_stmt || state->isa_stmt) { + subj = state->assign_to; + } + + if (state->isa_stmt && obj) { + ecs_parser_error(name, expr, column, + "invalid object in inheritance statement"); + return -1; + } + + if (state->isa_stmt) { + pred = ecs_pair(EcsIsA, pred); + } + + if (subj == EcsVariable) { + subj = pred; + } + + if (subj) { + ecs_id_t id; + if (!obj) { + id = term->id_flags | pred; + } else { + id = term->id_flags | ecs_pair(pred, obj); + state->last_object = obj; + } + state->last_assign_id = id; + state->last_predicate = pred; + state->last_subject = subj; + ecs_add_id(world, subj, id); + + pred_as_subj = false; + } else { + if (!obj) { + /* If no subject or object were provided, use predicate as subj + * unless the expression explicitly excluded the subject */ + if (pred_as_subj) { + state->last_subject = pred; + subj = pred; + } else { + state->last_predicate = pred; + pred_as_subj = false; + } + } else { + state->last_predicate = pred; + state->last_object = obj; + pred_as_subj = false; + } + } + + /* If this is a with clause (the list of entities between 'with' and scope + * open), add subject to the array of with frames */ + if (state->with_stmt) { + ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id; + + if (obj) { + id = ecs_pair(pred, obj); + } else { + id = pred; + } + + state->with[state->with_frame ++] = id; + } else { + if (subj && !state->scope_assign_stmt) { + plecs_apply_with_frame(world, state, subj); + } + } + + /* If an id was provided by itself, add default scope type to it */ + ecs_entity_t default_scope_type = state->default_scope_type[state->sp]; + if (pred_as_subj && default_scope_type) { + ecs_add_id(world, subj, default_scope_type); + } + + /* If annotations preceded the statement, append */ + if (!state->decl_type && state->annot_count) { + if (!subj) { + ecs_parser_error(name, expr, column, + "missing subject for annotations"); + return -1; + } + + plecs_apply_annotations(world, subj, state); + } + + return 0; +} + +static +const char* plecs_parse_inherit_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "cannot nest inheritance"); + return NULL; + } + + if (!state->last_subject) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign inheritance to"); + return NULL; + } + + state->isa_stmt = true; + state->assign_to = state->last_subject; + + return ptr; +} + +static +const char* plecs_parse_assign_var_expr( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state, + ecs_expr_var_t *var) +{ + ecs_value_t value = {0}; + + if (state->last_assign_id) { + value.type = state->last_assign_id; + value.ptr = ecs_value_new(world, state->last_assign_id); + if (!var && state->assembly_instance) { + var = ecs_vars_lookup(&state->vars, state->var_name); + } + } + + ptr = ecs_parse_expr(world, ptr, &value, + &(ecs_parse_expr_desc_t){ + .name = name, + .expr = expr, + .lookup_action = plecs_lookup_action, + .lookup_ctx = state, + .vars = &state->vars + }); + if (!ptr) { + if (state->last_assign_id) { + ecs_value_free(world, value.type, value.ptr); + } + goto error; + } + + if (var) { + bool ignore = state->var_is_prop && state->assembly_instance; + if (!ignore) { + if (var->value.ptr) { + ecs_value_free(world, var->value.type, var->value.ptr); + var->value.ptr = value.ptr; + var->value.type = value.type; + } + } else { + ecs_value_free(world, value.type, value.ptr); + } + } else { + var = ecs_vars_declare_w_value( + &state->vars, state->var_name, &value); + if (!var) { + goto error; + } + } + + state->var_is_prop = false; + return ptr; +error: + return NULL; +} + +static +const char* plecs_parse_assign_expr( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state, + ecs_expr_var_t *var) +{ + (void)world; + + if (state->var_stmt) { + return plecs_parse_assign_var_expr(world, name, expr, ptr, state, var); + } + + if (!state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "unexpected value outside of assignment statement"); + return NULL; + } + + ecs_id_t assign_id = state->last_assign_id; + if (!assign_id) { + ecs_parser_error(name, expr, ptr - expr, + "missing type for assignment statement"); + return NULL; + } + + ecs_entity_t assign_to = state->assign_to; + if (!assign_to) { + assign_to = state->last_subject; + } + + if (!assign_to) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign to"); + return NULL; + } + + ecs_entity_t type = ecs_get_typeid(world, assign_id); + if (!type) { + char *id_str = ecs_id_str(world, assign_id); + ecs_parser_error(name, expr, ptr - expr, + "invalid assignment, '%s' is not a type", id_str); + ecs_os_free(id_str); + return NULL; + } + + if (assign_to == EcsVariable) { + assign_to = type; + } + + void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); + + ptr = ecs_parse_expr(world, ptr, &(ecs_value_t){type, value_ptr}, + &(ecs_parse_expr_desc_t){ + .name = name, + .expr = expr, + .lookup_action = plecs_lookup_action, + .lookup_ctx = state, + .vars = &state->vars + }); + if (!ptr) { + return NULL; + } + + ecs_modified_id(world, assign_to, assign_id); + + return ptr; +} + +static +const char* plecs_parse_assign_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + (void)world; + + state->isa_stmt = false; + + /* Component scope (add components to entity) */ + if (!state->assign_to) { + if (!state->last_subject) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign to"); + return NULL; + } + state->assign_to = state->last_subject; + } + + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid assign statement in assign statement"); + return NULL; + } + + state->assign_stmt = true; + + /* Assignment without a preceding component */ + if (ptr[0] == '{') { + ecs_entity_t type = 0; + + /* If we're in a scope & last_subject is a type, assign to scope */ + if (ecs_get_scope(world) != 0) { + type = ecs_get_typeid(world, state->last_subject); + if (type != 0) { + type = state->last_subject; + } + } + + /* If type hasn't been set yet, check if scope has default type */ + if (!type && !state->scope_assign_stmt) { + type = state->default_scope_type[state->sp]; + } + + /* If no type has been found still, check if last with id is a type */ + if (!type && !state->scope_assign_stmt) { + int32_t with_frame_count = state->with_frames[state->sp]; + if (with_frame_count) { + type = state->with[with_frame_count - 1]; + } + } + + if (!type) { + ecs_parser_error(name, expr, ptr - expr, + "missing type for assignment"); + return NULL; + } + + state->last_assign_id = type; + } + + return ptr; +} + +static +const char* plecs_parse_assign_with_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + int32_t with_frame = state->with_frame - 1; + if (with_frame < 0) { + ecs_parser_error(name, expr, ptr - expr, + "missing type in with value"); + return NULL; + } + + ecs_id_t id = state->with[with_frame]; + ecs_id_record_t *idr = flecs_id_record_get(world, id); + const ecs_type_info_t *ti = idr ? idr->type_info : NULL; + if (!ti) { + char *typename = ecs_id_str(world, id); + ecs_parser_error(name, expr, ptr - expr, + "id '%s' in with value is not a type", typename); + ecs_os_free(typename); + return NULL; + } + + plecs_with_value_t *v = &state->with_value_frames[with_frame]; + v->value.type = ti->component; + v->value.ptr = ecs_value_new(world, ti->component); + v->owned = true; + if (!v->value.ptr) { + char *typename = ecs_id_str(world, id); + ecs_parser_error(name, expr, ptr - expr, + "failed to create value for '%s'", typename); + ecs_os_free(typename); + return NULL; + } + + ptr = ecs_parse_expr(world, ptr, &v->value, + &(ecs_parse_expr_desc_t){ + .name = name, + .expr = expr, + .lookup_action = plecs_lookup_action, + .lookup_ctx = state, + .vars = &state->vars + }); + if (!ptr) { + return NULL; + } + + return ptr; +} + +static +const char* plecs_parse_assign_with_var( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); + ecs_assert(state->with_stmt, ECS_INTERNAL_ERROR, NULL); + + char var_name[ECS_MAX_TOKEN_SIZE]; + const char *tmp = ptr; + ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); + if (!ptr) { + ecs_parser_error(name, expr, tmp - expr, + "unresolved variable '%s'", var_name); + return NULL; + } + + ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); + if (!var) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", var_name); + return NULL; + } + + int32_t with_frame = state->with_frame; + state->with[with_frame] = var->value.type; + state->with_value_frames[with_frame].value = var->value; + state->with_value_frames[with_frame].owned = false; + state->with_frame ++; + + return ptr; +} + +static +const char* plecs_parse_var_as_component( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); + ecs_assert(!state->var_stmt, ECS_INTERNAL_ERROR, NULL); + char var_name[ECS_MAX_TOKEN_SIZE]; + const char *tmp = ptr; + ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); + if (!ptr) { + ecs_parser_error(name, expr, tmp - expr, + "unresolved variable '%s'", var_name); + return NULL; + } + + ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); + if (!var) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", var_name); + return NULL; + } + + if (!state->assign_to) { + ecs_parser_error(name, expr, ptr - expr, + "missing lvalue for variable assignment '%s'", var_name); + return NULL; + } + + /* Use type of variable as component */ + ecs_entity_t type = var->value.type; + ecs_entity_t assign_to = state->assign_to; + if (!assign_to) { + assign_to = state->last_subject; + } + + void *dst = ecs_get_mut_id(world, assign_to, type); + if (!dst) { + char *type_name = ecs_get_fullpath(world, type); + ecs_parser_error(name, expr, ptr - expr, + "failed to obtain component for type '%s' of variable '%s'", + type_name, var_name); + ecs_os_free(type_name); + return NULL; + } + + if (ecs_value_copy(world, type, dst, var->value.ptr)) { + char *type_name = ecs_get_fullpath(world, type); + ecs_parser_error(name, expr, ptr - expr, + "failed to copy value for variable '%s' of type '%s'", + var_name, type_name); + ecs_os_free(type_name); + return NULL; + } + + ecs_modified_id(world, assign_to, type); + + return ptr; +} + +static +void plecs_push_using( + ecs_entity_t scope, + plecs_state_t *state) +{ + for (int i = 0; i < state->using_frame; i ++) { + if (state->using[i] == scope) { + return; + } + } + + state->using[state->using_frame ++] = scope; +} + +static +const char* plecs_parse_using_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt || state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid usage of using keyword"); + return NULL; + } + + char using_path[ECS_MAX_TOKEN_SIZE]; + const char *tmp = ptr + 1; + ptr = ecs_parse_token(name, expr, ptr + 5, using_path, 0); + if (!ptr) { + ecs_parser_error(name, expr, tmp - expr, + "expected identifier for using statement"); + return NULL; + } + + ecs_size_t len = ecs_os_strlen(using_path); + if (!len) { + ecs_parser_error(name, expr, tmp - expr, + "missing identifier for using statement"); + return NULL; + } + + /* Lookahead as * is not matched by parse_token */ + if (ptr[0] == '*') { + using_path[len] = '*'; + using_path[len + 1] = '\0'; + len ++; + ptr ++; + } + + ecs_entity_t scope; + if (len > 2 && !ecs_os_strcmp(&using_path[len - 2], ".*")) { + using_path[len - 2] = '\0'; + scope = ecs_lookup_fullpath(world, using_path); + if (!scope) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved identifier '%s' in using statement", using_path); + return NULL; + } + + /* Add each child of the scope to using stack */ + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t){ + .id = ecs_childof(scope) }); + while (ecs_term_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + plecs_push_using(it.entities[i], state); + } + } + } else { + scope = plecs_ensure_entity(world, state, using_path, 0, false); + if (!scope) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved identifier '%s' in using statement", using_path); + return NULL; + } + + plecs_push_using(scope, state); + } + + state->using_frames[state->sp] = state->using_frame; + return ptr; +} + +static +const char* plecs_parse_module_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + const char *expr_start = ecs_parse_ws_eol(expr); + if (expr_start != ptr) { + ecs_parser_error(name, expr, ptr - expr, + "module must be first statement of script"); + return NULL; + } + + char module_path[ECS_MAX_TOKEN_SIZE]; + const char *tmp = ptr + 1; + ptr = ecs_parse_token(name, expr, ptr + 6, module_path, 0); + if (!ptr) { + ecs_parser_error(name, expr, tmp - expr, + "expected identifier for module statement"); + return NULL; + } + + ecs_component_desc_t desc = {0}; + desc.entity = ecs_entity(world, { .name = module_path }); + ecs_entity_t module = ecs_module_init(world, NULL, &desc); + if (!module) { + return NULL; + } + + state->is_module = true; + state->sp ++; + state->scope[state->sp] = module; + ecs_set_scope(world, module); + return ptr; +} + +static +const char* plecs_parse_with_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with after inheritance"); + return NULL; + } + + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with in assign_stmt"); + return NULL; + } + + /* Add following expressions to with list */ + state->with_stmt = true; + return ptr + 5; +} + +static +const char* plecs_parse_assembly_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with after inheritance"); + return NULL; + } + + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with in assign_stmt"); + return NULL; + } + + state->assembly_stmt = true; + + return ptr + 9; +} + +static +const char* plecs_parse_var_type( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state, + ecs_entity_t *type_out) +{ + char prop_type_name[ECS_MAX_TOKEN_SIZE]; + const char *tmp = ptr + 1; + ptr = ecs_parse_token(name, expr, ptr + 1, prop_type_name, 0); + if (!ptr) { + ecs_parser_error(name, expr, tmp - expr, + "expected type for prop declaration"); + return NULL; + } + + ecs_entity_t prop_type = plecs_lookup(world, prop_type_name, state, 0, false); + if (!prop_type) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved property type '%s'", prop_type_name); + return NULL; + } + + *type_out = prop_type; + + return ptr; +} + +static +const char* plecs_parse_const_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + ptr = ecs_parse_token(name, expr, ptr + 5, state->var_name, 0); + if (!ptr) { + return NULL; + } + + ptr = ecs_parse_ws(ptr); + + if (ptr[0] == ':') { + ptr = plecs_parse_var_type( + world, name, expr, ptr, state, &state->last_assign_id); + if (!ptr) { + return NULL; + } + + ptr = ecs_parse_ws(ptr); + } + + if (ptr[0] != '=') { + ecs_parser_error(name, expr, ptr - expr, + "expected '=' after const declaration"); + return NULL; + } + + state->var_stmt = true; + return ptr + 1; +} + +static +const char* plecs_parse_prop_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + char prop_name[ECS_MAX_TOKEN_SIZE]; + ptr = ecs_parse_token(name, expr, ptr + 5, prop_name, 0); + if (!ptr) { + return NULL; + } + + ptr = ecs_parse_ws(ptr); + + if (ptr[0] != ':') { + ecs_parser_error(name, expr, ptr - expr, + "expected ':' after prop declaration"); + return NULL; + } + + ecs_entity_t prop_type; + ptr = plecs_parse_var_type(world, name, expr, ptr, state, &prop_type); + if (!ptr) { + return NULL; + } + + ecs_entity_t assembly = state->assembly; + if (!assembly) { + ecs_parser_error(name, expr, ptr - expr, + "unexpected prop '%s' outside of assembly", prop_name); + return NULL; + } + + if (!state->assembly_instance) { + ecs_entity_t prop_member = ecs_entity(world, { + .name = prop_name, + .add = { ecs_childof(assembly) } + }); + + if (!prop_member) { + return NULL; + } + + ecs_set(world, prop_member, EcsMember, { + .type = prop_type + }); + } + + if (ptr[0] != '=') { + ecs_parser_error(name, expr, ptr - expr, + "expected '=' after prop type"); + return NULL; + } + + ecs_os_strcpy(state->var_name, prop_name); + state->last_assign_id = prop_type; + state->var_stmt = true; + state->var_is_prop = true; + + return plecs_parse_fluff(ptr + 1); +} + +static +const char* plecs_parse_scope_open( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + state->isa_stmt = false; + + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid scope in assign_stmt"); + return NULL; + } + + state->sp ++; + + ecs_entity_t scope = 0; + ecs_entity_t default_scope_type = 0; + bool assembly_stmt = false; + + if (!state->with_stmt) { + if (state->last_subject) { + scope = state->last_subject; + ecs_set_scope(world, state->last_subject); + + /* Check if scope has a default child component */ + ecs_entity_t def_type_src = ecs_get_target_for_id(world, scope, + 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); + + if (def_type_src) { + default_scope_type = ecs_get_target( + world, def_type_src, EcsDefaultChildComponent, 0); + } + } else { + if (state->last_object) { + scope = ecs_pair( + state->last_predicate, state->last_object); + ecs_set_with(world, scope); + } else { + if (state->last_predicate) { + scope = ecs_pair(EcsChildOf, state->last_predicate); + } + ecs_set_scope(world, state->last_predicate); + } + } + + state->scope[state->sp] = scope; + state->default_scope_type[state->sp] = default_scope_type; + + if (state->assembly_stmt) { + assembly_stmt = true; + if (state->assembly) { + ecs_parser_error(name, expr, ptr - expr, + "invalid nested assembly"); + return NULL; + } + state->assembly = scope; + state->assembly_stmt = false; + state->assembly_start = ptr; + } + } else { + state->scope[state->sp] = state->scope[state->sp - 1]; + state->default_scope_type[state->sp] = + state->default_scope_type[state->sp - 1]; + state->assign_to = 0; + } + + state->using_frames[state->sp] = state->using_frame; + state->with_frames[state->sp] = state->with_frame; + state->with_stmt = false; + + ecs_vars_push(&state->vars); + + /* Declare variable to hold assembly instance during instantiation */ + if (assembly_stmt) { + ecs_value_t val = {0}; + ecs_expr_var_t *var = ecs_vars_declare_w_value( + &state->vars, "this", &val); + var->value.ptr = ECS_CONST_CAST(void*, &EcsThis); /* Dummy value */ + var->value.type = ecs_id(ecs_entity_t); + var->owned = false; + } + + return ptr; +} + +static +void plecs_free_with_frame( + ecs_world_t *world, + plecs_state_t *state) +{ + int32_t i, prev_with = state->with_frames[state->sp]; + for (i = prev_with; i < state->with_frame; i ++) { + plecs_with_value_t *v = &state->with_value_frames[i]; + if (!v->owned) { + continue; + } + if (v->value.type) { + ecs_value_free(world, v->value.type, v->value.ptr); + v->value.type = 0; + v->value.ptr = NULL; + v->owned = false; + } + } +} + +static +void plecs_free_all_with_frames( + ecs_world_t *world, + plecs_state_t *state) +{ + int32_t i; + for (i = state->sp - 1; i >= 0; i --) { + state->sp = i; + plecs_free_with_frame(world, state); + } +} + +static +const char* plecs_parse_scope_close( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid '}' after inheritance statement"); + return NULL; + } + + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "unfinished assignment before }"); + return NULL; + } + + ecs_entity_t cur = state->scope[state->sp], assembly = state->assembly; + if (state->sp && (cur == state->scope[state->sp - 1])) { + /* Previous scope is also from the assembly, not found the end yet */ + cur = 0; + } + if (cur && cur == assembly) { + ecs_size_t assembly_len = flecs_ito(ecs_size_t, ptr - state->assembly_start); + if (assembly_len) { + assembly_len --; + char *script = ecs_os_malloc_n(char, assembly_len + 1); + ecs_os_memcpy(script, state->assembly_start, assembly_len); + script[assembly_len] = '\0'; + state->assembly = 0; + state->assembly_start = NULL; + if (flecs_assembly_create(world, name, expr, ptr, assembly, script, state)) { + return NULL; + } + } else { + ecs_parser_error(name, expr, ptr - expr, "empty assembly"); + return NULL; + } + } + + state->scope[state->sp] = 0; + state->default_scope_type[state->sp] = 0; + state->sp --; + + if (state->sp < 0) { + ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); + return NULL; + } + + ecs_id_t id = state->scope[state->sp]; + if (!id || ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_set_with(world, id); + } + + if (!id || !ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_set_scope(world, id); + } + + plecs_free_with_frame(world, state); + + state->with_frame = state->with_frames[state->sp]; + state->using_frame = state->using_frames[state->sp]; + state->last_subject = 0; + state->assign_stmt = false; + + ecs_vars_pop(&state->vars); + + return plecs_parse_fluff(ptr + 1); +} + +static +const char *plecs_parse_plecs_term( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + ecs_term_t term = {0}; + ecs_entity_t decl_id = 0; + if (state->decl_stmt) { + decl_id = state->last_predicate; + } + + ptr = ecs_parse_term(world, name, expr, ptr, &term, NULL, NULL, false); + if (!ptr) { + return NULL; + } + + if (flecs_isident(ptr[0])) { + state->decl_type = true; + } + + if (!ecs_term_is_initialized(&term)) { + ecs_parser_error(name, expr, ptr - expr, "expected identifier"); + return NULL; /* No term found */ + } + + if (plecs_create_term(world, &term, name, expr, (ptr - expr), state)) { + ecs_term_fini(&term); + return NULL; /* Failed to create term */ + } + + if (decl_id && state->last_subject) { + ecs_add_id(world, state->last_subject, decl_id); + } + + state->decl_type = false; + + ecs_term_fini(&term); + + return ptr; +} + +static +const char* plecs_parse_annotation( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + do { + if(state->annot_count >= STACK_MAX_SIZE) { + ecs_parser_error(name, expr, ptr - expr, + "max number of annotations reached"); + return NULL; + } + + char ch; + const char *start = ptr; + for (; (ch = *ptr) && ch != '\n'; ptr ++) { } + + int32_t len = (int32_t)(ptr - start); + char *annot = ecs_os_malloc_n(char, len + 1); + ecs_os_memcpy_n(annot, start, char, len); + annot[len] = '\0'; + + state->annot[state->annot_count] = annot; + state->annot_count ++; + + ptr = plecs_parse_fluff(ptr); + } while (ptr[0] == '@'); + + return ptr; +} + +static +void plecs_clear_annotations( + plecs_state_t *state) +{ + int32_t i, count = state->annot_count; + for (i = 0; i < count; i ++) { + ecs_os_free(state->annot[i]); + } + state->annot_count = 0; +} + +static +const char* plecs_parse_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) +{ + state->assign_stmt = false; + state->scope_assign_stmt = false; + state->isa_stmt = false; + state->with_stmt = false; + state->decl_stmt = false; + state->var_stmt = false; + state->last_subject = 0; + state->last_predicate = 0; + state->last_object = 0; + state->assign_to = 0; + state->last_assign_id = 0; + + plecs_clear_annotations(state); + + ptr = plecs_parse_fluff(ptr); + + char ch = ptr[0]; + + if (!ch) { + goto done; + } else if (ch == '{') { + ptr = plecs_parse_fluff(ptr + 1); + goto scope_open; + } else if (ch == '}') { + goto scope_close; + } else if (ch == '-') { + ptr = plecs_parse_fluff(ptr + 1); + state->assign_to = ecs_get_scope(world); + state->scope_assign_stmt = true; + goto assign_stmt; + } else if (ch == '@') { + ptr = plecs_parse_annotation(name, expr, ptr, state); + if (!ptr) goto error; + goto term_expr; + } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { + ptr = plecs_parse_using_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; + } else if (!ecs_os_strncmp(ptr, TOK_MODULE " ", 6)) { + ptr = plecs_parse_module_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; + } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { + ptr = plecs_parse_with_stmt(name, expr, ptr, state); + if (!ptr) goto error; + goto term_expr; + } else if (!ecs_os_strncmp(ptr, TOK_CONST " ", 6)) { + ptr = plecs_parse_const_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; + goto assign_expr; + } else if (!ecs_os_strncmp(ptr, TOK_ASSEMBLY " ", 9)) { + ptr = plecs_parse_assembly_stmt(name, expr, ptr, state); + if (!ptr) goto error; + goto decl_stmt; + } else if (!ecs_os_strncmp(ptr, TOK_PROP " ", 5)) { + ptr = plecs_parse_prop_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; + goto assign_expr; + } else { + goto term_expr; + } + +term_expr: + if (!ptr[0]) { + goto done; + } + + if (ptr[0] == '$' && !isspace(ptr[1])) { + if (state->with_stmt) { + ptr = plecs_parse_assign_with_var(name, expr, ptr, state); + if (!ptr) { + return NULL; + } + } else if (!state->var_stmt) { + goto assign_var_as_component; + } + } else if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { + goto error; + } + + + const char *tptr = ecs_parse_ws(ptr); + if (flecs_isident(tptr[0])) { + if (state->decl_stmt) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected ' ' in declaration statement"); + goto error; + } + ptr = tptr; + goto decl_stmt; + } + +next_term: + ptr = plecs_parse_fluff(ptr); + + if (ptr[0] == ':' && ptr[1] == '-') { + ptr = plecs_parse_fluff(ptr + 2); + goto assign_stmt; + } else if (ptr[0] == ':') { + ptr = plecs_parse_fluff(ptr + 1); + goto inherit_stmt; + } else if (ptr[0] == ',') { + ptr = plecs_parse_fluff(ptr + 1); + goto term_expr; + } else if (ptr[0] == '{' || ptr[0] == '[') { + if (state->assign_stmt) { + goto assign_expr; + } else if (state->with_stmt && !isspace(ptr[-1])) { + /* If this is a { in a with statement which directly follows a + * non-whitespace character, the with id has a value */ + ptr = plecs_parse_assign_with_stmt(world, name, expr, ptr, state); + if (!ptr) { + goto error; + } + + goto next_term; + } else { + ptr = plecs_parse_fluff(ptr + 1); + goto scope_open; + } + } + + state->assign_stmt = false; + goto done; + +decl_stmt: + state->decl_stmt = true; + goto term_expr; + +inherit_stmt: + ptr = plecs_parse_inherit_stmt(name, expr, ptr, state); + if (!ptr) goto error; + + /* Expect base identifier */ + goto term_expr; + +assign_stmt: + ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; + + ptr = plecs_parse_fluff(ptr); + + /* Assignment without a preceding component */ + if (ptr[0] == '{' || ptr[0] == '[') { + goto assign_expr; + } + + /* Expect component identifiers */ + goto term_expr; + +assign_expr: + ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, NULL); + if (!ptr) goto error; + + ptr = plecs_parse_fluff(ptr); + if (ptr[0] == ',') { + ptr ++; + goto term_expr; + } else if (ptr[0] == '{') { + if (state->var_stmt) { + ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, state->var_name); + if (var && var->value.type == ecs_id(ecs_entity_t)) { + ecs_assert(var->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + /* The code contained an entity{...} variable assignment, use + * the assigned entity id as type for parsing the expression */ + state->last_assign_id = *(ecs_entity_t*)var->value.ptr; + ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, var); + goto done; + } + } + ecs_parser_error(name, expr, (ptr - expr), + "unexpected '{' after assignment"); + goto error; + } + + state->assign_stmt = false; + state->assign_to = 0; + goto done; + +assign_var_as_component: { + ptr = plecs_parse_var_as_component(world, name, expr, ptr, state); + if (!ptr) { + goto error; + } + state->assign_stmt = false; + state->assign_to = 0; + goto done; +} + +scope_open: + ptr = plecs_parse_scope_open(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; + +scope_close: + ptr = plecs_parse_scope_close(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; + +done: + return ptr; +error: + return NULL; +} + +static +int flecs_plecs_parse( + ecs_world_t *world, + const char *name, + const char *expr, + ecs_vars_t *vars, + ecs_entity_t script, + ecs_entity_t instance) +{ + const char *ptr = expr; + ecs_term_t term = {0}; + plecs_state_t state = {0}; + + if (!expr) { + return 0; + } + + state.scope[0] = 0; + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + ecs_entity_t prev_with = ecs_set_with(world, 0); + + if (ECS_IS_PAIR(prev_with) && ECS_PAIR_FIRST(prev_with) == EcsChildOf) { + ecs_set_scope(world, ECS_PAIR_SECOND(prev_with)); + state.scope[0] = ecs_pair_second(world, prev_with); + } else { + state.global_with = prev_with; + } + + ecs_vars_init(world, &state.vars); + + if (script) { + const EcsScript *s = ecs_get(world, script, EcsScript); + if (!s) { + ecs_err("%s: provided script entity is not a script", name); + goto error; + } + if (s && ecs_has(world, script, EcsStruct)) { + state.assembly = script; + state.assembly_instance = true; + + if (s->using_.count) { + ecs_os_memcpy_n(state.using, s->using_.array, + ecs_entity_t, s->using_.count); + state.using_frame = s->using_.count; + state.using_frames[0] = s->using_.count; + } + + if (instance) { + ecs_set_scope(world, instance); + } + } + } + + if (vars) { + state.vars.root.parent = vars->cur; + } + + do { + expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); + if (!ptr) { + goto error; + } + + if (!ptr[0]) { + break; /* End of expression */ + } + } while (true); + + ecs_set_scope(world, prev_scope); + ecs_set_with(world, prev_with); + plecs_clear_annotations(&state); + + if (state.is_module) { + state.sp --; + } + + if (state.sp != 0) { + ecs_parser_error(name, expr, 0, "missing end of scope"); + goto error; + } + + if (state.assign_stmt) { + ecs_parser_error(name, expr, 0, "unfinished assignment"); + goto error; + } + + if (state.errors) { + goto error; + } + + ecs_vars_fini(&state.vars); + + return 0; +error: + plecs_free_all_with_frames(world, &state); + ecs_vars_fini(&state.vars); + ecs_set_scope(world, state.scope[0]); + ecs_set_with(world, prev_with); + ecs_term_fini(&term); + return -1; +} + +int ecs_plecs_from_str( + ecs_world_t *world, + const char *name, + const char *expr) +{ + return flecs_plecs_parse(world, name, expr, NULL, 0, 0); +} + +static +char* flecs_load_from_file( + const char *filename) +{ + FILE* file; + char* content = NULL; + int32_t bytes; + size_t size; + + /* Open file for reading */ + ecs_os_fopen(&file, filename, "r"); + if (!file) { + ecs_err("%s (%s)", ecs_os_strerror(errno), filename); + goto error; + } + + /* Determine file size */ + fseek(file, 0 , SEEK_END); + bytes = (int32_t)ftell(file); + if (bytes == -1) { + goto error; + } + rewind(file); + + /* Load contents in memory */ + content = ecs_os_malloc(bytes + 1); + size = (size_t)bytes; + if (!(size = fread(content, 1, size, file)) && bytes) { + ecs_err("%s: read zero bytes instead of %d", filename, size); + ecs_os_free(content); + content = NULL; + goto error; + } else { + content[size] = '\0'; + } + + fclose(file); + + return content; +error: + ecs_os_free(content); + return NULL; +} + +int ecs_plecs_from_file( + ecs_world_t *world, + const char *filename) +{ + char *script = flecs_load_from_file(filename); + if (!script) { + return -1; + } + + int result = ecs_plecs_from_str(world, filename, script); + ecs_os_free(script); + return result; +} + +static +ecs_id_t flecs_script_tag( + ecs_entity_t script, + ecs_entity_t instance) +{ + if (!instance) { + return ecs_pair_t(EcsScript, script); + } else { + return ecs_pair(EcsChildOf, instance); + } +} + +void ecs_script_clear( + ecs_world_t *world, + ecs_entity_t script, + ecs_entity_t instance) +{ + ecs_delete_with(world, flecs_script_tag(script, instance)); +} + +int ecs_script_update( + ecs_world_t *world, + ecs_entity_t e, + ecs_entity_t instance, + const char *script, + ecs_vars_t *vars) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); + + int result = 0; + bool is_defer = ecs_is_deferred(world); + ecs_suspend_readonly_state_t srs; + ecs_world_t *real_world = NULL; + if (is_defer) { + ecs_assert(ecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); + real_world = flecs_suspend_readonly(world, &srs); + ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); + } + + ecs_script_clear(world, e, instance); + + EcsScript *s = ecs_get_mut(world, e, EcsScript); + if (!s->script || ecs_os_strcmp(s->script, script)) { + s->script = ecs_os_strdup(script); + ecs_modified(world, e, EcsScript); + } + + ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); + if (flecs_plecs_parse(world, ecs_get_name(world, e), script, vars, e, instance)) { + ecs_delete_with(world, ecs_pair_t(EcsScript, e)); + result = -1; + } + ecs_set_with(world, prev); + + if (is_defer) { + flecs_resume_readonly(real_world, &srs); + } + + return result; +} + +ecs_entity_t ecs_script_init( + ecs_world_t *world, + const ecs_script_desc_t *desc) +{ + const char *script = NULL; + ecs_entity_t e = desc->entity; + + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(desc != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!e) { + if (desc->filename) { + e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", NULL); + } else { + e = ecs_new_id(world); + } + } + + script = desc->str; + if (!script && desc->filename) { + script = flecs_load_from_file(desc->filename); + if (!script) { + goto error; + } + } + + if (ecs_script_update(world, e, 0, script, NULL)) { + goto error; + } + + if (script != desc->str) { + /* Safe cast, only happens when script is loaded from file */ + ecs_os_free(ECS_CONST_CAST(char*, script)); + } + + return e; +error: + if (script != desc->str) { + /* Safe cast, only happens when script is loaded from file */ + ecs_os_free(ECS_CONST_CAST(char*, script)); + } + if (!desc->entity) { + ecs_delete(world, e); + } + return 0; +} + +void FlecsScriptImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsScript); + ECS_IMPORT(world, FlecsMeta); +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsScript), + "Module with components for managing Flecs scripts"); +#endif + + ecs_set_name_prefix(world, "Ecs"); + ECS_COMPONENT_DEFINE(world, EcsScript); + + ecs_set_hooks(world, EcsScript, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsScript), + .dtor = ecs_dtor(EcsScript) + }); + + ecs_add_id(world, ecs_id(EcsScript), EcsTag); + ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); + + ecs_struct(world, { + .entity = ecs_id(EcsScript), + .members = { + { .name = "using", .type = ecs_vector(world, { + .entity = ecs_entity(world, { .name = "UsingVector" }), + .type = ecs_id(ecs_entity_t) + }), + .count = 0 + }, + { .name = "script", .type = ecs_id(ecs_string_t), .count = 0 } + } + }); +} + +#endif + +/** + * @file addons/rest.c + * @brief Rest addon. + */ + + +#ifdef FLECS_REST +/** + * @file json/json.h + * @brief Internal functions for JSON addon. + */ + + +#ifdef FLECS_JSON + +/* Deserialize from JSON */ +typedef enum ecs_json_token_t { + JsonObjectOpen, + JsonObjectClose, + JsonArrayOpen, + JsonArrayClose, + JsonColon, + JsonComma, + JsonNumber, + JsonString, + JsonTrue, + JsonFalse, + JsonNull, + JsonLargeInt, + JsonLargeString, + JsonInvalid +} ecs_json_token_t; + +typedef struct ecs_json_value_ser_ctx_t { + ecs_entity_t type; + const EcsMetaTypeSerialized *ser; + char *id_label; + bool initialized; +} ecs_json_value_ser_ctx_t; + +/* Cached data for serializer */ +typedef struct ecs_json_ser_ctx_t { + ecs_id_record_t *idr_doc_name; + ecs_id_record_t *idr_doc_color; + ecs_json_value_ser_ctx_t value_ctx[64]; +} ecs_json_ser_ctx_t; + +const char* flecs_json_parse( + const char *json, + ecs_json_token_t *token_kind, + char *token); + +const char* flecs_json_parse_large_string( + const char *json, + ecs_strbuf_t *buf); + +const char* flecs_json_expect( + const char *json, + ecs_json_token_t token_kind, + char *token, + const ecs_from_json_desc_t *desc); + +const char* flecs_json_expect_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); + +const char* flecs_json_expect_member_name( + const char *json, + char *token, + const char *member_name, + const ecs_from_json_desc_t *desc); + +const char* flecs_json_skip_object( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); + +const char* flecs_json_skip_array( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); + +/* Serialize to JSON */ +void flecs_json_next( + ecs_strbuf_t *buf); + +void flecs_json_number( + ecs_strbuf_t *buf, + double value); + +void flecs_json_true( + ecs_strbuf_t *buf); + +void flecs_json_false( + ecs_strbuf_t *buf); + +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value); + +void flecs_json_array_push( + ecs_strbuf_t *buf); + +void flecs_json_array_pop( + ecs_strbuf_t *buf); + +void flecs_json_object_push( + ecs_strbuf_t *buf); + +void flecs_json_object_pop( + ecs_strbuf_t *buf); + +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value); + +void flecs_json_string_escape( + ecs_strbuf_t *buf, + const char *value); + +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name); + +void flecs_json_membern( + ecs_strbuf_t *buf, + const char *name, + int32_t name_len); + +#define flecs_json_memberl(buf, name)\ + flecs_json_membern(buf, name, sizeof(name) - 1) + +void flecs_json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id); + +void flecs_json_id_member( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id); + +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind); + +bool flecs_json_serialize_get_field_ctx( + const ecs_world_t *world, + const ecs_iter_t *it, + int32_t f, + ecs_json_ser_ctx_t *ser_ctx); + +int flecs_json_serialize_iter_result_rows( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx); + +bool flecs_json_serialize_iter_result_is_set( + const ecs_iter_t *it, + ecs_strbuf_t *buf); + +bool flecs_json_skip_variable( + const char *name); + +int json_ser_type( + const ecs_world_t *world, + const ecs_vec_t *ser, + const void *base, + ecs_strbuf_t *str); + +#endif + + +static ECS_TAG_DECLARE(EcsRestPlecs); + +typedef struct { + ecs_world_t *world; + ecs_http_server_t *srv; + int32_t rc; +} ecs_rest_ctx_t; + +static ECS_COPY(EcsRest, dst, src, { + ecs_rest_ctx_t *impl = src->impl; + if (impl) { + impl->rc ++; + } + + ecs_os_strset(&dst->ipaddr, src->ipaddr); + dst->port = src->port; + dst->impl = impl; +}) + +static ECS_MOVE(EcsRest, dst, src, { + *dst = *src; + src->ipaddr = NULL; + src->impl = NULL; +}) + +static ECS_DTOR(EcsRest, ptr, { + ecs_rest_ctx_t *impl = ptr->impl; + if (impl) { + impl->rc --; + if (!impl->rc) { + ecs_http_server_fini(impl->srv); + ecs_os_free(impl); + } + } + ecs_os_free(ptr->ipaddr); +}) + +static char *rest_last_err; +static ecs_os_api_log_t rest_prev_log; + +static +void flecs_rest_capture_log( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + (void)file; (void)line; + +#ifdef FLECS_DEBUG + if (level < 0) { + /* Also log to previous log function in debug mode */ + if (rest_prev_log) { + ecs_log_enable_colors(true); + rest_prev_log(level, file, line, msg); + ecs_log_enable_colors(false); + } + } +#endif + + if (!rest_last_err && level < 0) { + rest_last_err = ecs_os_strdup(msg); + } +} + +static +char* flecs_rest_get_captured_log(void) { + char *result = rest_last_err; + rest_last_err = NULL; + return result; +} + +static +void flecs_reply_verror( + ecs_http_reply_t *reply, + const char *fmt, + va_list args) +{ + ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); + ecs_strbuf_vappend(&reply->body, fmt, args); + ecs_strbuf_appendlit(&reply->body, "\"}"); +} + +static +void flecs_reply_error( + ecs_http_reply_t *reply, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + flecs_reply_verror(reply, fmt, args); + va_end(args); +} + +static +void flecs_rest_bool_param( + const ecs_http_request_t *req, + const char *name, + bool *value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + if (!ecs_os_strcmp(value, "true")) { + value_out[0] = true; + } else { + value_out[0] = false; + } + } +} + +static +void flecs_rest_int_param( + const ecs_http_request_t *req, + const char *name, + int32_t *value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = atoi(value); + } +} + +static +void flecs_rest_string_param( + const ecs_http_request_t *req, + const char *name, + char **value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = ECS_CONST_CAST(char*, value); + } +} + +static +void flecs_rest_parse_json_ser_entity_params( + ecs_world_t *world, + ecs_entity_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + flecs_rest_bool_param(req, "path", &desc->serialize_path); + flecs_rest_bool_param(req, "label", &desc->serialize_label); + flecs_rest_bool_param(req, "brief", &desc->serialize_brief); + flecs_rest_bool_param(req, "link", &desc->serialize_link); + flecs_rest_bool_param(req, "color", &desc->serialize_color); + flecs_rest_bool_param(req, "ids", &desc->serialize_ids); + flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); + flecs_rest_bool_param(req, "base", &desc->serialize_base); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "private", &desc->serialize_private); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "matches", &desc->serialize_matches); + flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); + + char *rel = NULL; + flecs_rest_string_param(req, "refs", &rel); + if (rel) { + desc->serialize_refs = ecs_lookup_fullpath(world, rel); + } +} + +static +void flecs_rest_parse_json_ser_iter_params( + ecs_iter_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); + flecs_rest_bool_param(req, "term_labels", &desc->serialize_term_labels); + flecs_rest_bool_param(req, "ids", &desc->serialize_ids); + flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); + flecs_rest_bool_param(req, "sources", &desc->serialize_sources); + flecs_rest_bool_param(req, "variables", &desc->serialize_variables); + flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "private", &desc->serialize_private); + flecs_rest_bool_param(req, "entities", &desc->serialize_entities); + flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); + flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); + flecs_rest_bool_param(req, "variable_ids", &desc->serialize_variable_ids); + flecs_rest_bool_param(req, "colors", &desc->serialize_colors); + flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); + flecs_rest_bool_param(req, "table", &desc->serialize_table); + flecs_rest_bool_param(req, "rows", &desc->serialize_rows); + bool results = true; + flecs_rest_bool_param(req, "results", &results); + desc->dont_serialize_results = !results; +} + +static +bool flecs_rest_reply_entity( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + char *path = &req->path[7]; + ecs_dbg_2("rest: request entity '%s'", path); + + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + ecs_dbg_2("rest: entity '%s' not found", path); + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + return true; + } + + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + flecs_rest_parse_json_ser_entity_params(world, &desc, req); + if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) { + ecs_strbuf_reset(&reply->body); + reply->code = 500; + reply->status = "Internal server error"; + return true; + } + return true; +} + +static +bool flecs_rest_reply_world( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; + if (ecs_world_to_json_buf(world, &reply->body, NULL) != 0) { + ecs_strbuf_reset(&reply->body); + reply->code = 500; + reply->status = "Internal server error"; + return true; + } + return true; +} + +static +ecs_entity_t flecs_rest_entity_from_path( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + } + return e; +} + +static +bool flecs_rest_set( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + const char *data = ecs_http_get_param(req, "data"); + ecs_from_json_desc_t desc = {0}; + desc.expr = data; + desc.name = path; + if (ecs_entity_from_json(world, e, data, &desc) == NULL) { + flecs_reply_error(reply, "invalid request"); + reply->code = 400; + return true; + } + + return true; +} + +static +bool flecs_rest_delete( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + ecs_delete(world, e); + + return true; +} + +static +bool flecs_rest_enable( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path, + bool enable) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + ecs_enable(world, e, enable); + + return true; +} + +static +bool flecs_rest_script( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)world; + (void)req; + (void)reply; +#ifdef FLECS_PLECS + const char *data = ecs_http_get_param(req, "data"); + if (!data) { + flecs_reply_error(reply, "missing data parameter"); + return true; + } + + bool prev_color = ecs_log_enable_colors(false); + rest_prev_log = ecs_os_api.log_; + ecs_os_api.log_ = flecs_rest_capture_log; + + ecs_entity_t script = ecs_script(world, { + .entity = ecs_entity(world, { .name = "scripts.main" }), + .str = data + }); + + if (!script) { + char *err = flecs_rest_get_captured_log(); + char *escaped_err = ecs_astresc('"', err); + flecs_reply_error(reply, escaped_err); + reply->code = 400; /* bad request */ + ecs_os_free(escaped_err); + ecs_os_free(err); + } + + ecs_os_api.log_ = rest_prev_log; + ecs_log_enable_colors(prev_color); + + return true; +#else + return false; +#endif +} + +static +void flecs_rest_reply_set_captured_log( + ecs_http_reply_t *reply) +{ + char *err = flecs_rest_get_captured_log(); + if (err) { + char *escaped_err = ecs_astresc('"', err); + flecs_reply_error(reply, escaped_err); + ecs_os_free(escaped_err); + ecs_os_free(err); + } + + reply->code = 400; +} + +static +void flecs_rest_iter_to_reply( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + ecs_iter_t *it) +{ + ecs_iter_to_json_desc_t desc = {0}; + desc.serialize_entities = true; + desc.serialize_variables = true; + flecs_rest_parse_json_ser_iter_params(&desc, req); + + int32_t offset = 0; + int32_t limit = 1000; + + flecs_rest_int_param(req, "offset", &offset); + flecs_rest_int_param(req, "limit", &limit); + + if (offset < 0 || limit < 0) { + flecs_reply_error(reply, "invalid offset/limit parameter"); + return; + } + + ecs_iter_t pit = ecs_page_iter(it, offset, limit); + if (ecs_iter_to_json_buf(world, &pit, &reply->body, &desc)) { + flecs_rest_reply_set_captured_log(reply); + } +} + +static +bool flecs_rest_reply_existing_query( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *name) +{ + ecs_entity_t q = ecs_lookup_fullpath(world, name); + if (!q) { + flecs_reply_error(reply, "unresolved identifier '%s'", name); + reply->code = 404; + return true; + } + + const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); + if (!poly) { + flecs_reply_error(reply, + "resolved identifier '%s' is not a query", name); + reply->code = 400; + return true; + } + + ecs_iter_t it; + ecs_iter_poly(world, poly->poly, &it, NULL); + + ecs_dbg_2("rest: request query '%s'", q); + bool prev_color = ecs_log_enable_colors(false); + rest_prev_log = ecs_os_api.log_; + ecs_os_api.log_ = flecs_rest_capture_log; + + const char *vars = ecs_http_get_param(req, "vars"); + if (vars) { + if (!ecs_poly_is(poly->poly, ecs_rule_t)) { + flecs_reply_error(reply, + "variables are only supported for rule queries"); + reply->code = 400; + return true; + } + if (ecs_rule_parse_vars(poly->poly, &it, vars) == NULL) { + flecs_rest_reply_set_captured_log(reply); + return true; + } + } + + flecs_rest_iter_to_reply(world, req, reply, &it); + + ecs_os_api.log_ = rest_prev_log; + ecs_log_enable_colors(prev_color); + + return true; +} + +static +bool flecs_rest_reply_query( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + const char *q_name = ecs_http_get_param(req, "name"); + if (q_name) { + return flecs_rest_reply_existing_query(world, req, reply, q_name); + } + + const char *q = ecs_http_get_param(req, "q"); + if (!q) { + ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); + reply->code = 400; /* bad request */ + return true; + } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); + + ecs_dbg_2("rest: request query '%s'", q); + bool prev_color = ecs_log_enable_colors(false); + rest_prev_log = ecs_os_api.log_; + ecs_os_api.log_ = flecs_rest_capture_log; + + ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ + .expr = q + }); + if (!r) { + flecs_rest_reply_set_captured_log(reply); + if (try) { + /* If client is trying queries, don't spam console with errors */ + reply->code = 200; + } + } else { + ecs_iter_t it = ecs_rule_iter(world, r); + flecs_rest_iter_to_reply(world, req, reply, &it); + ecs_rule_fini(r); + } + + ecs_os_api.log_ = rest_prev_log; + ecs_log_enable_colors(prev_color); + + return true; +} + +static +bool flecs_rest_reply_query_plan( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + const char *q_name = ecs_http_get_param(req, "name"); + if (q_name) { + reply->code = 400; + ecs_strbuf_appendlit(&reply->body, + "query plan endpoint unsupported for named queries"); + return true; + } + + const char *q = ecs_http_get_param(req, "q"); + if (!q) { + ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); + reply->code = 400; /* bad request */ + return true; + } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); + + ecs_dbg_2("rest: request query plan for '%s'", q); + bool prev_color = ecs_log_enable_colors(false); + rest_prev_log = ecs_os_api.log_; + ecs_os_api.log_ = flecs_rest_capture_log; + + ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ + .expr = q + }); + if (!r) { + flecs_rest_reply_set_captured_log(reply); + if (try) { + /* If client is trying queries, don't spam console with errors */ + reply->code = 200; + } + } else { + ecs_log_enable_colors(true); + char *plan = ecs_rule_str(r); + ecs_strbuf_appendlit(&reply->body, "{\"content\":"); + flecs_json_string_escape(&reply->body, plan); + ecs_strbuf_appendlit(&reply->body, "}"); + ecs_os_free(plan); + ecs_rule_fini(r); + } + + ecs_os_api.log_ = rest_prev_log; + ecs_log_enable_colors(prev_color); + + return true; +} + +#ifdef FLECS_MONITOR + +static +void flecs_rest_array_append_( + ecs_strbuf_t *reply, + const char *field, + int32_t field_len, + const ecs_float_t *values, + int32_t t) +{ + ecs_strbuf_list_appendch(reply, '"'); + ecs_strbuf_appendstrn(reply, field, field_len); + ecs_strbuf_appendlit(reply, "\":"); + ecs_strbuf_list_push(reply, "[", ","); + + int32_t i; + for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { + int32_t index = i % ECS_STAT_WINDOW; + ecs_strbuf_list_next(reply); + ecs_strbuf_appendflt(reply, (double)values[index], '"'); + } + + ecs_strbuf_list_pop(reply, "]"); +} + +#define flecs_rest_array_append(reply, field, values, t)\ + flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t) + +static +void flecs_rest_gauge_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t field_len, + int32_t t, + const char *brief, + int32_t brief_len) +{ + ecs_strbuf_list_appendch(reply, '"'); + ecs_strbuf_appendstrn(reply, field, field_len); + ecs_strbuf_appendlit(reply, "\":"); + ecs_strbuf_list_push(reply, "{", ","); + + flecs_rest_array_append(reply, "avg", m->gauge.avg, t); + flecs_rest_array_append(reply, "min", m->gauge.min, t); + flecs_rest_array_append(reply, "max", m->gauge.max, t); + + if (brief) { + ecs_strbuf_list_appendlit(reply, "\"brief\":\""); + ecs_strbuf_appendstrn(reply, brief, brief_len); + ecs_strbuf_appendch(reply, '"'); + } + + ecs_strbuf_list_pop(reply, "}"); +} + +static +void flecs_rest_counter_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t field_len, + int32_t t, + const char *brief, + int32_t brief_len) +{ + flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); +} + +#define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ + flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) + +#define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ + flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) + +#define ECS_GAUGE_APPEND(reply, s, field, brief)\ + ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) + +#define ECS_COUNTER_APPEND(reply, s, field, brief)\ + ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) + +static +void flecs_world_stats_to_json( + ecs_strbuf_t *reply, + const EcsWorldStats *monitor_stats) +{ + const ecs_world_stats_t *stats = &monitor_stats->stats; + + ecs_strbuf_list_push(reply, "{", ","); + ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); + ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); + + ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); + ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); + + ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.get_mut_count, "Get_mut commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); + ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); + ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); + + ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); + ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); + ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); + ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); + ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); + ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); + + ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); + ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); + ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); + ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); + + ECS_GAUGE_APPEND(reply, stats, components.tag_count, "Tag ids in use"); + ECS_GAUGE_APPEND(reply, stats, components.component_count, "Component ids in use"); + ECS_GAUGE_APPEND(reply, stats, components.pair_count, "Pair ids in use"); + ECS_GAUGE_APPEND(reply, stats, components.type_count, "Registered component types"); + ECS_COUNTER_APPEND(reply, stats, components.create_count, "Number of new component, tag and pair ids created"); + ECS_COUNTER_APPEND(reply, stats, components.delete_count, "Number of component, pair and tag ids deleted"); + + ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); + ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); + ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); + + ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); + ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); + ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); + ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); + ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); + ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); + ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); + ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); + ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); + ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); + + ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); + ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); + ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); + ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); + ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); + ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); + ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); + ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); + ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); + + ecs_strbuf_list_pop(reply, "}"); +} + +static +void flecs_system_stats_to_json( + ecs_world_t *world, + ecs_strbuf_t *reply, + ecs_entity_t system, + const ecs_system_stats_t *stats) +{ + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"name\":\""); + ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); + ecs_strbuf_appendch(reply, '"'); + + if (!stats->task) { + ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); + ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); + } + + ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); + ecs_strbuf_list_pop(reply, "}"); +} + +static +void flecs_pipeline_stats_to_json( + ecs_world_t *world, + ecs_strbuf_t *reply, + const EcsPipelineStats *stats) +{ + ecs_strbuf_list_push(reply, "[", ","); + + int32_t i, count = ecs_vec_count(&stats->stats.systems), sync_cur = 0; + ecs_entity_t *ids = ecs_vec_first_t(&stats->stats.systems, ecs_entity_t); + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; + + ecs_strbuf_list_next(reply); + + if (id) { + ecs_system_stats_t *sys_stats = ecs_map_get_deref( + &stats->stats.system_stats, ecs_system_stats_t, id); + flecs_system_stats_to_json(world, reply, id, sys_stats); + } else { + /* Sync point */ + ecs_strbuf_list_push(reply, "{", ","); + ecs_sync_stats_t *sync_stats = ecs_vec_get_t( + &stats->stats.sync_points, ecs_sync_stats_t, sync_cur); + + ecs_strbuf_list_appendlit(reply, "\"system_count\":"); + ecs_strbuf_appendint(reply, sync_stats->system_count); + + ecs_strbuf_list_appendlit(reply, "\"multi_threaded\":"); + ecs_strbuf_appendbool(reply, sync_stats->multi_threaded); + + ecs_strbuf_list_appendlit(reply, "\"no_readonly\":"); + ecs_strbuf_appendbool(reply, sync_stats->no_readonly); + + ECS_GAUGE_APPEND_T(reply, sync_stats, + time_spent, stats->stats.t, ""); + ECS_GAUGE_APPEND_T(reply, sync_stats, + commands_enqueued, stats->stats.t, ""); + + ecs_strbuf_list_pop(reply, "}"); + sync_cur ++; + } + } + + ecs_strbuf_list_pop(reply, "]"); +} + +static +bool flecs_rest_reply_stats( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + char *period_str = NULL; + flecs_rest_string_param(req, "period", &period_str); + char *category = &req->path[6]; + + ecs_entity_t period = EcsPeriod1s; + if (period_str) { + char *period_name = ecs_asprintf("Period%s", period_str); + period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); + ecs_os_free(period_name); + if (!period) { + flecs_reply_error(reply, "bad request (invalid period string)"); + reply->code = 400; + return false; + } + } + + if (!ecs_os_strcmp(category, "world")) { + const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, + EcsWorldStats, period); + flecs_world_stats_to_json(&reply->body, stats); + return true; + + } else if (!ecs_os_strcmp(category, "pipeline")) { + const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, + EcsPipelineStats, period); + flecs_pipeline_stats_to_json(world, &reply->body, stats); + return true; + + } else { + flecs_reply_error(reply, "bad request (unsupported category)"); + reply->code = 400; + return false; + } +} +#else +static +bool flecs_rest_reply_stats( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)world; + (void)req; + (void)reply; + return false; +} +#endif + +static +void flecs_rest_reply_table_append_type( + ecs_world_t *world, + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + ecs_strbuf_list_push(reply, "[", ","); + int32_t i, count = table->type.count; + ecs_id_t *ids = table->type.array; + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(reply); + ecs_strbuf_appendch(reply, '"'); + ecs_id_str_buf(world, ids[i], reply); + ecs_strbuf_appendch(reply, '"'); + } + ecs_strbuf_list_pop(reply, "]"); +} + +static +void flecs_rest_reply_table_append_memory( + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + int32_t used = 0, allocated = 0; + + used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); + allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); + + int32_t i, storage_count = table->column_count; + ecs_column_t *columns = table->data.columns; + + for (i = 0; i < storage_count; i ++) { + used += columns[i].data.count * columns[i].ti->size; + allocated += columns[i].data.size * columns[i].ti->size; + } + + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_append(reply, "\"used\":%d", used); + ecs_strbuf_list_append(reply, "\"allocated\":%d", allocated); + ecs_strbuf_list_pop(reply, "}"); +} + +static +void flecs_rest_reply_table_append( + ecs_world_t *world, + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + ecs_strbuf_list_next(reply); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_append(reply, "\"id\":%u", (uint32_t)table->id); + ecs_strbuf_list_appendstr(reply, "\"type\":"); + flecs_rest_reply_table_append_type(world, reply, table); + ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table)); + ecs_strbuf_list_append(reply, "\"memory\":"); + flecs_rest_reply_table_append_memory(reply, table); + ecs_strbuf_list_pop(reply, "}"); +} + +static +bool flecs_rest_reply_tables( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; + + ecs_strbuf_list_push(&reply->body, "[", ","); + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + flecs_rest_reply_table_append(world, &reply->body, table); + } + ecs_strbuf_list_pop(&reply->body, "]"); + + return true; +} + +static +bool flecs_rest_reply( + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + void *ctx) +{ + ecs_rest_ctx_t *impl = ctx; + ecs_world_t *world = impl->world; + + if (req->path == NULL) { + ecs_dbg("rest: bad request (missing path)"); + flecs_reply_error(reply, "bad request (missing path)"); + reply->code = 400; + return false; + } + + if (req->method == EcsHttpGet) { + /* Entity endpoint */ + if (!ecs_os_strncmp(req->path, "entity/", 7)) { + return flecs_rest_reply_entity(world, req, reply); + + /* Query endpoint */ + } else if (!ecs_os_strcmp(req->path, "query")) { + return flecs_rest_reply_query(world, req, reply); + + /* Query plan endpoint */ + } else if (!ecs_os_strcmp(req->path, "query_plan")) { + return flecs_rest_reply_query_plan(world, req, reply); + + /* World endpoint */ + } else if (!ecs_os_strcmp(req->path, "world")) { + return flecs_rest_reply_world(world, req, reply); + + /* Stats endpoint */ + } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { + return flecs_rest_reply_stats(world, req, reply); + + /* Tables endpoint */ + } else if (!ecs_os_strncmp(req->path, "tables", 6)) { + return flecs_rest_reply_tables(world, req, reply); + } + + } else if (req->method == EcsHttpPut) { + /* Set endpoint */ + if (!ecs_os_strncmp(req->path, "set/", 4)) { + return flecs_rest_set(world, req, reply, &req->path[4]); + + /* Delete endpoint */ + } else if (!ecs_os_strncmp(req->path, "delete/", 7)) { + return flecs_rest_delete(world, reply, &req->path[7]); + + /* Enable endpoint */ + } else if (!ecs_os_strncmp(req->path, "enable/", 7)) { + return flecs_rest_enable(world, reply, &req->path[7], true); + + /* Disable endpoint */ + } else if (!ecs_os_strncmp(req->path, "disable/", 8)) { + return flecs_rest_enable(world, reply, &req->path[8], false); + + /* Script endpoint */ + } else if (!ecs_os_strncmp(req->path, "script", 6)) { + return flecs_rest_script(world, req, reply); + } + } + + return false; +} + +ecs_http_server_t* ecs_rest_server_init( + ecs_world_t *world, + const ecs_http_server_desc_t *desc) +{ + ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); + ecs_http_server_desc_t private_desc = {0}; + if (desc) { + private_desc = *desc; + } + private_desc.callback = flecs_rest_reply; + private_desc.ctx = srv_ctx; + + ecs_http_server_t *srv = ecs_http_server_init(&private_desc); + if (!srv) { + ecs_os_free(srv_ctx); + return NULL; + } + + srv_ctx->world = world; + srv_ctx->srv = srv; + srv_ctx->rc = 1; + srv_ctx->srv = srv; + return srv; +} + +void ecs_rest_server_fini( + ecs_http_server_t *srv) +{ + ecs_rest_ctx_t *srv_ctx = ecs_http_server_ctx(srv); + ecs_os_free(srv_ctx); + ecs_http_server_fini(srv); +} + +static +void flecs_on_set_rest(ecs_iter_t *it) { + EcsRest *rest = it->ptrs[0]; + + int i; + for(i = 0; i < it->count; i ++) { + if (!rest[i].port) { + rest[i].port = ECS_REST_DEFAULT_PORT; + } + + ecs_http_server_t *srv = ecs_rest_server_init(it->real_world, + &(ecs_http_server_desc_t){ + .ipaddr = rest[i].ipaddr, + .port = rest[i].port, + .cache_timeout = 1.0 + }); + + if (!srv) { + const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; + ecs_err("failed to create REST server on %s:%u", + ipaddr, rest[i].port); + continue; + } + + rest[i].impl = ecs_http_server_ctx(srv); + + ecs_http_server_start(srv); + } +} + +static +void DequeueRest(ecs_iter_t *it) { + EcsRest *rest = ecs_field(it, EcsRest, 1); + + if (it->delta_system_time > (ecs_ftime_t)1.0) { + ecs_warn( + "detected large progress interval (%.2fs), REST request may timeout", + (double)it->delta_system_time); + } + + int32_t i; + for(i = 0; i < it->count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + if (ctx) { + ecs_http_server_dequeue(ctx->srv, it->delta_time); + } + } +} + +static +void DisableRest(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){ + .id = ecs_id(EcsRest), + .src.flags = EcsSelf + }); + + if (it->event == EcsOnAdd) { + /* REST module was disabled */ + while (ecs_term_next(&rit)) { + EcsRest *rest = ecs_field(&rit, EcsRest, 1); + int i; + for (i = 0; i < rit.count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + ecs_http_server_stop(ctx->srv); + } + } + } else if (it->event == EcsOnRemove) { + /* REST module was enabled */ + while (ecs_term_next(&rit)) { + EcsRest *rest = ecs_field(&rit, EcsRest, 1); + int i; + for (i = 0; i < rit.count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + ecs_http_server_start(ctx->srv); + } + } + } +} + +void FlecsRestImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsRest); + + ECS_IMPORT(world, FlecsPipeline); +#ifdef FLECS_PLECS + ECS_IMPORT(world, FlecsScript); +#endif +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsRest), + "Module that implements Flecs REST API"); +#endif + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsRest); + + ecs_set_hooks(world, EcsRest, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsRest), + .copy = ecs_copy(EcsRest), + .dtor = ecs_dtor(EcsRest), + .on_set = flecs_on_set_rest + }); + + ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); + + ecs_system(world, { + .entity = ecs_id(DequeueRest), + .no_readonly = true + }); + + ecs_observer(world, { + .filter = { + .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = DisableRest + }); + + ecs_set_name_prefix(world, "EcsRest"); + ECS_TAG_DEFINE(world, EcsRestPlecs); +} + +#endif + +/** + * @file addons/snapshot.c + * @brief Snapshot addon. + */ + + +#ifdef FLECS_SNAPSHOT + + +/* World snapshot */ +struct ecs_snapshot_t { + ecs_world_t *world; + ecs_entity_index_t entity_index; + ecs_vec_t tables; + uint64_t last_id; +}; + +/** 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; + +static +ecs_data_t* flecs_duplicate_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *main_data) +{ + int32_t count = ecs_table_count(table); + if (!count) { + return NULL; + } + + ecs_data_t *result = ecs_os_calloc_t(ecs_data_t); + int32_t i, column_count = table->column_count; + result->columns = flecs_wdup_n(world, ecs_column_t, column_count, + main_data->columns); + + /* Copy entities */ + ecs_allocator_t *a = &world->allocator; + result->entities = ecs_vec_copy_shrink_t(a, &main_data->entities, ecs_entity_t); + + /* Copy each column */ + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &result->columns[i]; + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t size = ti->size; + ecs_copy_t copy = ti->hooks.copy_ctor; + if (copy) { + void *src_ptr = ecs_vec_first(&column->data); + + ecs_vec_t dst; + ecs_vec_init(a, &dst, size, count); + ecs_vec_set_count(a, &dst, size, count); + void *dst_ptr = ecs_vec_first(&dst); + + copy(dst_ptr, src_ptr, count, ti); + + column->data = dst; + } else { + column->data = ecs_vec_copy_shrink(a, &column->data, size); + } + } + + return result; +} + +static +void snapshot_table( + const ecs_world_t *world, + ecs_snapshot_t *snapshot, + ecs_table_t *table) +{ + if (table->flags & EcsTableHasBuiltins) { + return; + } + + ecs_table_leaf_t *l = ecs_vec_get_t( + &snapshot->tables, ecs_table_leaf_t, (int32_t)table->id); + ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); + + l->table = table; + l->type = flecs_type_copy( + ECS_CONST_CAST(ecs_world_t*, world), &table->type); + l->data = flecs_duplicate_data( + ECS_CONST_CAST(ecs_world_t*, world), table, &table->data); +} + +static +ecs_snapshot_t* snapshot_create( + const ecs_world_t *world, + const ecs_entity_index_t *entity_index, + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + ecs_run_aperiodic(ECS_CONST_CAST(ecs_world_t*, world), 0); + + result->world = ECS_CONST_CAST(ecs_world_t*, 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) { + flecs_entities_copy(&result->entity_index, entity_index); + } + + /* Create vector with as many elements as tables, so we can store the + * snapshot tables at their element ids. When restoring a snapshot, the code + * will run a diff between the tables in the world and the snapshot, to see + * which of the world tables still exist, no longer exist, or need to be + * deleted. */ + uint64_t t, table_count = flecs_sparse_last_id(&world->store.tables) + 1; + ecs_vec_init_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); + ecs_vec_set_count_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); + ecs_table_leaf_t *arr = ecs_vec_first_t(&result->tables, ecs_table_leaf_t); + + /* Array may have holes, so initialize with 0 */ + ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); + + /* Iterate tables in iterator */ + if (iter) { + while (next(iter)) { + ecs_table_t *table = iter->table; + snapshot_table(world, result, table); + } + } else { + for (t = 1; t < table_count; t ++) { + ecs_table_t *table = flecs_sparse_get_t( + &world->store.tables, ecs_table_t, t); + snapshot_table(world, result, table); + } + } + + return result; +} + +/** Create a snapshot */ +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); + + ecs_snapshot_t *result = snapshot_create( + world, ecs_eis(world), NULL, NULL); + + result->last_id = flecs_entities_max_id(world); + + return result; +} + +/** Create a filtered snapshot */ +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter) +{ + ecs_world_t *world = iter->world; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_snapshot_t *result = snapshot_create( + world, ecs_eis(world), iter, iter ? iter->next : NULL); + + result->last_id = flecs_entities_max_id(world); + + return result; +} + +/* Restoring an unfiltered snapshot restores the world to the exact state it was + * when the snapshot was taken. */ +static +void restore_unfiltered( + ecs_world_t *world, + ecs_snapshot_t *snapshot) +{ + flecs_entity_index_restore(ecs_eis(world), &snapshot->entity_index); + flecs_entity_index_fini(&snapshot->entity_index); + + flecs_entities_max_id(world) = snapshot->last_id; + + ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); + int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables); + int32_t snapshot_count = ecs_vec_count(&snapshot->tables); + + for (i = 1; i <= count; i ++) { + ecs_table_t *world_table = flecs_sparse_get_t( + &world->store.tables, ecs_table_t, (uint32_t)i); + + if (world_table && (world_table->flags & EcsTableHasBuiltins)) { + continue; + } + + ecs_table_leaf_t *snapshot_table = NULL; + if (i < snapshot_count) { + snapshot_table = &leafs[i]; + if (!snapshot_table->table) { + snapshot_table = NULL; + } + } + + /* If the world table no longer exists but the snapshot table does, + * reinsert it */ + if (!world_table && snapshot_table) { + ecs_table_t *table = flecs_table_find_or_create(world, + &snapshot_table->type); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (snapshot_table->data) { + flecs_table_replace_data(world, table, snapshot_table->data); + } + + /* If the world table still exists, replace its data */ + } else if (world_table && snapshot_table) { + ecs_assert(snapshot_table->table == world_table, + ECS_INTERNAL_ERROR, NULL); + + if (snapshot_table->data) { + flecs_table_replace_data( + world, world_table, snapshot_table->data); + } else { + flecs_table_clear_data( + world, world_table, &world_table->data); + flecs_table_init_data(world, world_table); + } + + /* If the snapshot table doesn't exist, this table was created after the + * snapshot was taken and needs to be deleted */ + } else if (world_table && !snapshot_table) { + /* Deleting a table invokes OnRemove triggers & updates the entity + * index. That is not what we want, since entities may no longer be + * valid (if they don't exist in the snapshot) or may have been + * restored in a different table. Therefore first clear the data + * from the table (which doesn't invoke triggers), and then delete + * the table. */ + flecs_table_clear_data(world, world_table, &world_table->data); + flecs_delete_table(world, world_table); + + /* If there is no world & snapshot table, nothing needs to be done */ + } else { } + + if (snapshot_table) { + ecs_os_free(snapshot_table->data); + flecs_type_free(world, &snapshot_table->type); + } + } + + /* Now that all tables have been restored and world is in a consistent + * state, run OnSet systems */ + int32_t world_count = flecs_sparse_count(&world->store.tables); + for (i = 0; i < world_count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t( + &world->store.tables, ecs_table_t, i); + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + int32_t tcount = ecs_table_count(table); + if (tcount) { + int32_t j, storage_count = table->column_count; + for (j = 0; j < storage_count; j ++) { + ecs_type_t type = { + .array = &table->data.columns[j].id, + .count = 1 + }; + flecs_notify_on_set(world, table, 0, tcount, &type, true); + } + } + } +} + +/* Restoring a filtered snapshots only restores the entities in the snapshot + * to their previous state. */ +static +void restore_filtered( + ecs_world_t *world, + ecs_snapshot_t *snapshot) +{ + ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); + int32_t l = 0, snapshot_count = ecs_vec_count(&snapshot->tables); + + for (l = 0; l < snapshot_count; l ++) { + ecs_table_leaf_t *snapshot_table = &leafs[l]; + ecs_table_t *table = snapshot_table->table; + + if (!table) { + continue; + } + + ecs_data_t *data = snapshot_table->data; + if (!data) { + flecs_type_free(world, &snapshot_table->type); + continue; + } + + /* Delete entity from storage first, so that when we restore it to the + * current table we can be sure that there won't be any duplicates */ + int32_t i, entity_count = ecs_vec_count(&data->entities); + ecs_entity_t *entities = ecs_vec_first( + &snapshot_table->data->entities); + for (i = 0; i < entity_count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *r = flecs_entities_try(world, e); + if (r && r->table) { + flecs_table_delete(world, r->table, + ECS_RECORD_TO_ROW(r->row), true); + } else { + /* Make sure that the entity has the same generation count */ + flecs_entities_set_generation(world, e); + } + } + + /* Merge data from snapshot table with world table */ + int32_t old_count = ecs_table_count(snapshot_table->table); + int32_t new_count = flecs_table_data_count(snapshot_table->data); + + flecs_table_merge(world, table, table, &table->data, snapshot_table->data); + + /* Run OnSet systems for merged entities */ + if (new_count) { + int32_t j, storage_count = table->column_count; + for (j = 0; j < storage_count; j ++) { + ecs_type_t type = { + .array = &table->data.columns[j].id, + .count = 1 + }; + flecs_notify_on_set( + world, table, old_count, new_count, &type, true); + } + } + + flecs_wfree_n(world, ecs_column_t, table->column_count, + snapshot_table->data->columns); + ecs_os_free(snapshot_table->data); + flecs_type_free(world, &snapshot_table->type); + } +} + +/** Restore a snapshot */ +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot) +{ + ecs_run_aperiodic(world, 0); + + if (flecs_entity_index_count(&snapshot->entity_index) > 0) { + /* Unfiltered snapshots have a copy of the entity index which is + * copied back entirely when the snapshot is restored */ + restore_unfiltered(world, snapshot); + } else { + restore_filtered(world, snapshot); + } + + ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); + + ecs_os_free(snapshot); +} + +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot) +{ + ecs_snapshot_iter_t iter = { + .tables = snapshot->tables, + .index = 0 + }; + + return (ecs_iter_t){ + .world = snapshot->world, + .table_count = ecs_vec_count(&snapshot->tables), + .priv.iter.snapshot = iter, + .next = ecs_snapshot_next + }; +} + +bool ecs_snapshot_next( + ecs_iter_t *it) +{ + ecs_snapshot_iter_t *iter = &it->priv.iter.snapshot; + ecs_table_leaf_t *tables = ecs_vec_first_t(&iter->tables, ecs_table_leaf_t); + int32_t count = ecs_vec_count(&iter->tables); + int32_t i; + + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = tables[i].table; + if (!table) { + continue; + } + + ecs_data_t *data = tables[i].data; + + it->table = table; + it->count = ecs_table_count(table); + if (data) { + it->entities = ecs_vec_first(&data->entities); + } else { + it->entities = NULL; + } + + ECS_BIT_SET(it->flags, EcsIterIsValid); + iter->index = i + 1; + + goto yield; + } + + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + return false; + +yield: + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + return true; +} + +/** Cleanup snapshot */ +void ecs_snapshot_free( + ecs_snapshot_t *snapshot) +{ + flecs_entity_index_fini(&snapshot->entity_index); + + ecs_table_leaf_t *tables = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); + int32_t i, count = ecs_vec_count(&snapshot->tables); + for (i = 0; i < count; i ++) { + ecs_table_leaf_t *snapshot_table = &tables[i]; + ecs_table_t *table = snapshot_table->table; + if (table) { + ecs_data_t *data = snapshot_table->data; + if (data) { + flecs_table_clear_data(snapshot->world, table, data); + ecs_os_free(data); + } + flecs_type_free(snapshot->world, &snapshot_table->type); + } + } + + ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); + ecs_os_free(snapshot); +} + +#endif + +/** + * @file addons/stats.c + * @brief Stats addon. + */ + + +#ifdef FLECS_SYSTEM +/** + * @file addons/system/system.h + * @brief Internal types and functions for system addon. + */ + +#ifndef FLECS_SYSTEM_PRIVATE_H +#define FLECS_SYSTEM_PRIVATE_H + +#ifdef FLECS_SYSTEM + + +#define ecs_system_t_magic (0x65637383) +#define ecs_system_t_tag EcsSystem + +extern ecs_mixins_t ecs_system_t_mixins; + +typedef struct ecs_system_t { + ecs_header_t hdr; + + ecs_run_action_t run; /* See ecs_system_desc_t */ + ecs_iter_action_t action; /* See ecs_system_desc_t */ + + ecs_query_t *query; /* System query */ + ecs_entity_t query_entity; /* Entity associated with query */ + ecs_entity_t tick_source; /* Tick source associated with system */ + + /* Schedule parameters */ + bool multi_threaded; + bool no_readonly; + + int64_t invoke_count; /* Number of times system is invoked */ + ecs_ftime_t time_spent; /* Time spent on running system */ + ecs_ftime_t time_passed; /* Time passed since last invocation */ + int64_t last_frame; /* Last frame for which the system was considered */ + + void *ctx; /* Userdata for system */ + void *binding_ctx; /* Optional language binding context */ + + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t binding_ctx_free; + + /* Mixins */ + ecs_world_t *world; + ecs_entity_t entity; + ecs_poly_dtor_t dtor; +} ecs_system_t; + +/* Invoked when system becomes active / inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const ecs_system_t *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, + ecs_system_t *system_data, + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t delta_time, + int32_t offset, + int32_t limit, + void *param); + +#endif + +#endif + +#endif + +#ifdef FLECS_PIPELINE +/** + * @file addons/pipeline/pipeline.h + * @brief Internal functions/types for pipeline addon. + */ + +#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. */ +typedef struct ecs_pipeline_op_t { + int32_t offset; /* Offset in systems vector */ + int32_t count; /* Number of systems to run before next op */ + double time_spent; /* Time spent merging commands for sync point */ + int64_t commands_enqueued; /* Number of commands enqueued for sync point */ + bool multi_threaded; /* Whether systems can be ran multi threaded */ + bool no_readonly; /* Whether systems are staged or not */ +} ecs_pipeline_op_t; + +struct ecs_pipeline_state_t { + ecs_query_t *query; /* Pipeline query */ + ecs_vec_t ops; /* Pipeline schedule */ + ecs_vec_t systems; /* Vector with system ids */ + + ecs_entity_t last_system; /* Last system ran by pipeline */ + ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ + int32_t match_count; /* Used to track of rebuild is necessary */ + int32_t rebuild_count; /* Number of pipeline rebuilds */ + ecs_iter_t *iters; /* Iterator for worker(s) */ + int32_t iter_count; + + /* Members for continuing pipeline iteration after pipeline rebuild */ + ecs_pipeline_op_t *cur_op; /* Current pipeline op */ + int32_t cur_i; /* Index in current result */ + int32_t ran_since_merge; /* Index in current op */ + bool no_readonly; /* Is pipeline in readonly mode */ +}; + +typedef struct EcsPipeline { + /* Stable ptr so threads can safely access while entity/components move */ + ecs_pipeline_state_t *state; +} EcsPipeline; + +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// + +bool flecs_pipeline_update( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + bool start_of_frame); + +void flecs_run_pipeline( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time); + +int32_t flecs_run_pipeline_ops( + ecs_world_t* world, + ecs_stage_t* stage, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time); + +//////////////////////////////////////////////////////////////////////////////// +//// Worker API +//////////////////////////////////////////////////////////////////////////////// + +void flecs_workers_progress( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time); + +void flecs_create_worker_threads( + ecs_world_t *world); + +void flecs_join_worker_threads( + ecs_world_t *world); + +void flecs_signal_workers( + ecs_world_t *world); + +void flecs_wait_for_sync( + ecs_world_t *world); + +#endif + +#endif + +#ifdef FLECS_STATS + +#define ECS_GAUGE_RECORD(m, t, value)\ + flecs_gauge_record(m, t, (ecs_float_t)(value)) + +#define ECS_COUNTER_RECORD(m, t, value)\ + flecs_counter_record(m, t, (double)(value)) + +#define ECS_METRIC_FIRST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) + +#define ECS_METRIC_LAST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) + +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 flecs_gauge_record( + ecs_metric_t *m, + int32_t t, + ecs_float_t value) +{ + m->gauge.avg[t] = value; + m->gauge.min[t] = value; + m->gauge.max[t] = value; +} + +static +double flecs_counter_record( + ecs_metric_t *m, + int32_t t, + double value) +{ + int32_t tp = t_prev(t); + double prev = m->counter.value[tp]; + m->counter.value[t] = value; + double gauge_value = value - prev; + if (gauge_value < 0) { + gauge_value = 0; /* Counters are monotonically increasing */ + } + flecs_gauge_record(m, t, (ecs_float_t)gauge_value); + return gauge_value; +} + +static +void flecs_metric_print( + const char *name, + ecs_float_t value) +{ + ecs_size_t len = ecs_os_strlen(name); + ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); +} + +static +void flecs_gauge_print( + const char *name, + int32_t t, + const ecs_metric_t *m) +{ + flecs_metric_print(name, m->gauge.avg[t]); +} + +static +void flecs_counter_print( + const char *name, + int32_t t, + const ecs_metric_t *m) +{ + flecs_metric_print(name, m->counter.rate.avg[t]); +} + +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, + int32_t t_dst, + int32_t t_src) +{ + ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); + + bool min_set = false; + dst->gauge.avg[t_dst] = 0; + dst->gauge.min[t_dst] = 0; + dst->gauge.max[t_dst] = 0; + + ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; + + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; + + if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { + dst->gauge.min[t_dst] = src->gauge.min[t]; + min_set = true; + } + if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { + dst->gauge.max[t_dst] = src->gauge.max[t]; + } + } + + dst->counter.value[t_dst] = src->counter.value[t_src]; + +error: + return; +} + +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t prev, + int32_t count) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t t = t_next(prev); + + if (m->gauge.min[t] < m->gauge.min[prev]) { + m->gauge.min[prev] = m->gauge.min[t]; + } + + if (m->gauge.max[t] > m->gauge.max[prev]) { + m->gauge.max[prev] = m->gauge.max[t]; + } + + ecs_float_t fcount = (ecs_float_t)(count + 1); + ecs_float_t cur = m->gauge.avg[prev]; + ecs_float_t next = m->gauge.avg[t]; + + cur *= ((fcount - 1) / fcount); + next *= 1 / fcount; + + m->gauge.avg[prev] = cur + next; + m->counter.value[prev] = m->counter.value[t]; + +error: + return; +} + +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src) +{ + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); + + m->gauge.avg[dst] = m->gauge.avg[src]; + m->gauge.min[dst] = m->gauge.min[src]; + m->gauge.max[dst] = m->gauge.max[src]; + m->counter.value[dst] = m->counter.value[src]; + +error: + return; +} + +static +void flecs_stats_reduce( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) +{ + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); + } +} + +static +void flecs_stats_reduce_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src, + int32_t count) +{ + int32_t t_dst_next = t_next(t_dst); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + /* Reduce into previous value */ + ecs_metric_reduce_last(dst_cur, t_dst, count); + + /* Restore old value */ + dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; + } +} + +static +void flecs_stats_repeat_last( + ecs_metric_t *cur, + ecs_metric_t *last, + int32_t t) +{ + int32_t prev = t_prev(t); + for (; cur <= last; cur ++) { + ecs_metric_copy(cur, t, prev); + } +} + +static +void flecs_stats_copy_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) +{ + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; + } +} + +void ecs_world_stats_get( + const ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + int32_t t = s->t = t_next(s->t); + + double delta_frame_count = + ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); + ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); + ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); + ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); + ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); + ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); + ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); + + double delta_world_time = + ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); + ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); + ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); + ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); + ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); + ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); + ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); + ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); + if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { + ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); + } else { + ECS_GAUGE_RECORD(&s->performance.fps, t, 0); + } + + ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); + ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); + + ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_sparse_count(&world->type_info)); + ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); + + ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); + ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); + if (ecs_is_alive(world, EcsSystem)) { + ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); + } + ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); + ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); + ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); + ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); + + ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); + ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); + ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); + ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); + ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); + ECS_COUNTER_RECORD(&s->commands.get_mut_count, t, world->info.cmd.get_mut_count); + ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); + ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); + ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); + ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); + ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); + + int64_t outstanding_allocs = ecs_os_api_malloc_count + + ecs_os_api_calloc_count - ecs_os_api_free_count; + ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); + ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); + ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); + ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); + + outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); + + outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); + +#ifdef FLECS_HTTP + ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); + ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); + ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); + ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); + ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); + ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); + ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); + ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); + ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); +#endif + +error: + return; +} + +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} + +void ecs_world_stats_reduce_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src, + int32_t count) +{ + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); +} + +void ecs_world_stats_repeat_last( + ecs_world_stats_t *stats) +{ + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); +} + +void ecs_world_stats_copy_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); +} + +void ecs_query_stats_get( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + int32_t t = s->t = t_next(s->t); + + if (query->filter.flags & EcsFilterMatchThis) { + ECS_GAUGE_RECORD(&s->matched_entity_count, t, + ecs_query_entity_count(query)); + ECS_GAUGE_RECORD(&s->matched_table_count, t, + ecs_query_table_count(query)); + ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, + ecs_query_empty_table_count(query)); + } else { + ECS_GAUGE_RECORD(&s->matched_entity_count, t, 0); + ECS_GAUGE_RECORD(&s->matched_table_count, t, 0); + ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, 0); + } + +error: + return; +} + +void ecs_query_stats_reduce( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} + +void ecs_query_stats_reduce_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src, + int32_t count) +{ + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); +} + +void ecs_query_stats_repeat_last( + ecs_query_stats_t *stats) +{ + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); +} + +void ecs_query_stats_copy_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) +{ + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); +} + +#ifdef FLECS_SYSTEM + +bool ecs_system_stats_get( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t); + if (!ptr) { + return false; + } + + ecs_query_stats_get(world, ptr->query, &s->query); + int32_t t = s->query.t; + + ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); + ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); + + s->task = !(ptr->query->filter.flags & EcsFilterMatchThis); + + return true; +error: + return false; +} + +void ecs_system_stats_reduce( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) +{ + ecs_query_stats_reduce(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t); +} + +void ecs_system_stats_reduce_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src, + int32_t count) +{ + ecs_query_stats_reduce_last(&dst->query, &src->query, count); + dst->task = src->task; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); +} + +void ecs_system_stats_repeat_last( + ecs_system_stats_t *stats) +{ + ecs_query_stats_repeat_last(&stats->query); + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->query.t)); +} + +void ecs_system_stats_copy_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) +{ + ecs_query_stats_copy_last(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); +} + +#endif + +#ifdef FLECS_PIPELINE + +bool ecs_pipeline_stats_get( + ecs_world_t *stage, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); + if (!pqc) { + return false; + } + ecs_pipeline_state_t *pq = pqc->state; + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t sys_count = 0, active_sys_count = 0; + + /* Count number of active systems */ + ecs_iter_t it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } + active_sys_count += it.count; + } + + /* Count total number of systems in pipeline */ + it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + sys_count += it.count; + } + + /* Also count synchronization points */ + ecs_vec_t *ops = &pq->ops; + ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); + int32_t pip_count = active_sys_count + ecs_vec_count(ops); + + if (!sys_count) { + return false; + } + + if (ecs_map_is_init(&s->system_stats) && !sys_count) { + ecs_map_fini(&s->system_stats); + } + ecs_map_init_if(&s->system_stats, NULL); + + if (op) { + ecs_entity_t *systems = NULL; + if (pip_count) { + ecs_vec_init_if_t(&s->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); + systems = ecs_vec_first_t(&s->systems, ecs_entity_t); + + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(stage, pq->query); + + int32_t i, i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { + continue; + } + + 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 */ + } + } + } + + systems[i_system ++] = 0; /* Last merge */ + ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); + } + + /* Get sync point statistics */ + int32_t i, count = ecs_vec_count(ops); + if (count) { + ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); + op = ecs_vec_first_t(ops, ecs_pipeline_op_t); + + for (i = 0; i < count; i ++) { + ecs_pipeline_op_t *cur = &op[i]; + ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, + ecs_sync_stats_t, i); + + ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); + ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, + cur->commands_enqueued); + + el->system_count = cur->count; + el->multi_threaded = cur->multi_threaded; + el->no_readonly = cur->no_readonly; + } + } + } + + /* Separately populate system stats map from build query, which includes + * systems that aren't currently active */ + it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_system_stats_t *stats = ecs_map_ensure_alloc_t(&s->system_stats, + ecs_system_stats_t, it.entities[i]); + stats->query.t = s->t; + ecs_system_stats_get(world, it.entities[i], stats); + } + } + + s->t = t_next(s->t); + + return true; +error: + return false; +} + +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats) +{ + ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); + while (ecs_map_next(&it)) { + ecs_system_stats_t *elem = ecs_map_ptr(&it); + ecs_os_free(elem); + } + ecs_map_fini(&stats->system_stats); + ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); + ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); +} + +void ecs_pipeline_stats_reduce( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) +{ + int32_t system_count = ecs_vec_count(&src->systems); + ecs_vec_init_if_t(&dst->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); + ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); + ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); + ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); + + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->no_readonly = src_el->no_readonly; + } + + ecs_map_init_if(&dst->system_stats, NULL); + ecs_map_iter_t it = ecs_map_iter(&src->system_stats); + + while (ecs_map_next(&it)) { + ecs_system_stats_t *sys_src = ecs_map_ptr(&it); + ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, + ecs_system_stats_t, ecs_map_key(&it)); + sys_dst->query.t = dst->t; + ecs_system_stats_reduce(sys_dst, sys_src); + } + dst->t = t_next(dst->t); +} + +void ecs_pipeline_stats_reduce_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src, + int32_t count) +{ + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t, count); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->no_readonly = src_el->no_readonly; + } + + ecs_map_init_if(&dst->system_stats, NULL); + ecs_map_iter_t it = ecs_map_iter(&src->system_stats); + while (ecs_map_next(&it)) { + ecs_system_stats_t *sys_src = ecs_map_ptr(&it); + ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, + ecs_system_stats_t, ecs_map_key(&it)); + sys_dst->query.t = dst->t; + ecs_system_stats_reduce_last(sys_dst, sys_src, count); + } + dst->t = t_prev(dst->t); +} + +void ecs_pipeline_stats_repeat_last( + ecs_pipeline_stats_t *stats) +{ + int32_t i, sync_count = ecs_vec_count(&stats->sync_points); + ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *el = &syncs[i]; + flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), + (stats->t)); + } + + ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); + while (ecs_map_next(&it)) { + ecs_system_stats_t *sys = ecs_map_ptr(&it); + sys->query.t = stats->t; + ecs_system_stats_repeat_last(sys); + } + stats->t = t_next(stats->t); +} + +void ecs_pipeline_stats_copy_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) +{ + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->no_readonly = src_el->no_readonly; + } + + ecs_map_init_if(&dst->system_stats, NULL); + + ecs_map_iter_t it = ecs_map_iter(&src->system_stats); + while (ecs_map_next(&it)) { + ecs_system_stats_t *sys_src = ecs_map_ptr(&it); + ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, + ecs_system_stats_t, ecs_map_key(&it)); + sys_dst->query.t = dst->t; + ecs_system_stats_copy_last(sys_dst, sys_src); + } +} + +#endif + +void ecs_world_stats_log( + const ecs_world_t *world, + const ecs_world_stats_t *s) +{ + int32_t t = s->t; + + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + flecs_counter_print("Frame", t, &s->frame.frame_count); + ecs_trace("-------------------------------------"); + flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); + flecs_counter_print("systems ran", t, &s->frame.systems_ran); + ecs_trace(""); + flecs_metric_print("target FPS", world->info.target_fps); + flecs_metric_print("time scale", world->info.time_scale); + ecs_trace(""); + flecs_gauge_print("actual FPS", t, &s->performance.fps); + flecs_counter_print("frame time", t, &s->performance.frame_time); + flecs_counter_print("system time", t, &s->performance.system_time); + flecs_counter_print("merge time", t, &s->performance.merge_time); + flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); + ecs_trace(""); + flecs_gauge_print("tag id count", t, &s->components.tag_count); + flecs_gauge_print("component id count", t, &s->components.component_count); + flecs_gauge_print("pair id count", t, &s->components.pair_count); + flecs_gauge_print("type count", t, &s->components.type_count); + flecs_counter_print("id create count", t, &s->components.create_count); + flecs_counter_print("id delete count", t, &s->components.delete_count); + ecs_trace(""); + flecs_gauge_print("alive entity count", t, &s->entities.count); + flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); + ecs_trace(""); + flecs_gauge_print("query count", t, &s->queries.query_count); + flecs_gauge_print("observer count", t, &s->queries.observer_count); + flecs_gauge_print("system count", t, &s->queries.system_count); + ecs_trace(""); + flecs_gauge_print("table count", t, &s->tables.count); + flecs_gauge_print("empty table count", t, &s->tables.empty_count); + flecs_counter_print("table create count", t, &s->tables.create_count); + flecs_counter_print("table delete count", t, &s->tables.delete_count); + ecs_trace(""); + flecs_counter_print("add commands", t, &s->commands.add_count); + flecs_counter_print("remove commands", t, &s->commands.remove_count); + flecs_counter_print("delete commands", t, &s->commands.delete_count); + flecs_counter_print("clear commands", t, &s->commands.clear_count); + flecs_counter_print("set commands", t, &s->commands.set_count); + flecs_counter_print("get_mut commands", t, &s->commands.get_mut_count); + flecs_counter_print("modified commands", t, &s->commands.modified_count); + flecs_counter_print("other commands", t, &s->commands.other_count); + flecs_counter_print("discarded commands", t, &s->commands.discard_count); + flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); + flecs_counter_print("batched commands", t, &s->commands.batched_count); + ecs_trace(""); + +error: + return; +} + +#endif + +/** + * @file addons/timer.c + * @brief Timer addon. + */ + + +#ifdef FLECS_TIMER + +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_field(it, EcsTimer, 1); + EcsTickSource *tick_source = ecs_field(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; + } + + const ecs_world_info_t *info = ecs_get_world_info(it->world); + ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; + ecs_ftime_t timeout = timer[i].timeout; + + if (time_elapsed >= timeout) { + ecs_ftime_t 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 - timer[i].overshoot; + timer[i].overshoot = t; + + 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_field(it, EcsRateFilter, 1); + EcsTickSource *tick_dst = ecs_field(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; + } + } 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; + } + } +} + +static +void ProgressTickSource(ecs_iter_t *it) { + EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1); + + /* If tick source has no filters, tick unconditionally */ + int i; + for (i = 0; i < it->count; i ++) { + tick_src[i].tick = true; + tick_src[i].time_elapsed = it->delta_time; + } +} + +ecs_entity_t ecs_set_timeout( + ecs_world_t *world, + ecs_entity_t timer, + ecs_ftime_t timeout) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = timeout, + .single_shot = true, + .active = true + }); + + ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); + if (system_data) { + system_data->tick_source = timer; + } + +error: + return timer; +} + +ecs_ftime_t ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } +error: + return 0; +} + +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + ecs_ftime_t interval) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + timer = ecs_new(world, EcsTimer); + } + + EcsTimer *t = ecs_get_mut(world, timer, EcsTimer); + ecs_check(t != NULL, ECS_INVALID_PARAMETER, NULL); + t->timeout = interval; + t->active = true; + ecs_modified(world, timer, EcsTimer); + + ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); + if (system_data) { + system_data->tick_source = timer; + } +error: + return timer; +} + +ecs_ftime_t ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + return 0; + } + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } +error: + return 0; +} + +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ptr->active = true; + ptr->time = 0; +error: + return; +} + +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ptr->active = false; +error: + return; +} + +void ecs_reset_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ptr->time = 0; +error: + return; +} + +ecs_entity_t ecs_set_rate( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + filter = ecs_set(world, filter, EcsRateFilter, { + .rate = rate, + .src = source + }); + + ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t); + if (system_data) { + system_data->tick_source = filter; + } + +error: + return filter; +} + +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); + ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + system_data->tick_source = tick_source; +error: + return; +} + +static +void RandomizeTimers(ecs_iter_t *it) { + EcsTimer *timer = ecs_field(it, EcsTimer, 1); + int32_t i; + for (i = 0; i < it->count; i ++) { + timer[i].time = + ((ecs_ftime_t)rand() / (ecs_ftime_t)RAND_MAX) * timer[i].timeout; + } +} + +void ecs_randomize_timers( + ecs_world_t *world) +{ + ecs_observer(world, { + .entity = ecs_entity(world, { .name = "flecs.timer.RandomizeTimers" }), + .filter.terms = {{ + .id = ecs_id(EcsTimer) + }}, + .events = {EcsOnSet}, + .yield_existing = true, + .callback = RandomizeTimers + }); +} + +void FlecsTimerImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsTimer); + ECS_IMPORT(world, FlecsPipeline); +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsTimer), + "Module that implements system timers (used by .interval)"); +#endif + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsTimer); + flecs_bootstrap_component(world, EcsRateFilter); + + ecs_set_hooks(world, EcsTimer, { + .ctor = ecs_default_ctor + }); + + /* Add EcsTickSource to timers and rate filters */ + ecs_system(world, { + .entity = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(EcsPreFrame) }}), + .query.filter.terms = { + { .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, + { .id = ecs_id(EcsRateFilter), .oper = EcsAnd, .inout = EcsIn }, + { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} + }, + .callback = AddTickSource + }); + + /* Timer handling */ + ecs_system(world, { + .entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}), + .query.filter.terms = { + { .id = ecs_id(EcsTimer) }, + { .id = ecs_id(EcsTickSource) } + }, + .callback = ProgressTimers + }); + + /* Rate filter handling */ + ecs_system(world, { + .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}), + .query.filter.terms = { + { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, + { .id = ecs_id(EcsTickSource), .inout = EcsOut } + }, + .callback = ProgressRateFilters + }); + + /* TickSource without a timer or rate filter just increases each frame */ + ecs_system(world, { + .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}), + .query.filter.terms = { + { .id = ecs_id(EcsTickSource), .inout = EcsOut }, + { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, + { .id = ecs_id(EcsTimer), .oper = EcsNot } + }, + .callback = ProgressTickSource + }); +} + +#endif + +/** + * @file addons/units.c + * @brief Units addon. + */ + + +#ifdef FLECS_UNITS + +void FlecsUnitsImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsUnits); + +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsUnits), + "Module with (amongst others) SI units for annotating component members"); +#endif + + ecs_set_name_prefix(world, "Ecs"); + + EcsUnitPrefixes = ecs_entity(world, { + .name = "prefixes", + .add = { EcsModule } + }); + + /* Initialize unit prefixes */ + + ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); + + EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yocto" }), + .symbol = "y", + .translation = { .factor = 10, .power = -24 } + }); + EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zepto" }), + .symbol = "z", + .translation = { .factor = 10, .power = -21 } + }); + EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Atto" }), + .symbol = "a", + .translation = { .factor = 10, .power = -18 } + }); + EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Femto" }), + .symbol = "a", + .translation = { .factor = 10, .power = -15 } + }); + EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Pico" }), + .symbol = "p", + .translation = { .factor = 10, .power = -12 } + }); + EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Nano" }), + .symbol = "n", + .translation = { .factor = 10, .power = -9 } + }); + EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Micro" }), + .symbol = "μ", + .translation = { .factor = 10, .power = -6 } + }); + EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Milli" }), + .symbol = "m", + .translation = { .factor = 10, .power = -3 } + }); + EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Centi" }), + .symbol = "c", + .translation = { .factor = 10, .power = -2 } + }); + EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Deci" }), + .symbol = "d", + .translation = { .factor = 10, .power = -1 } + }); + EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Deca" }), + .symbol = "da", + .translation = { .factor = 10, .power = 1 } + }); + EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Hecto" }), + .symbol = "h", + .translation = { .factor = 10, .power = 2 } + }); + EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Kilo" }), + .symbol = "k", + .translation = { .factor = 10, .power = 3 } + }); + EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Mega" }), + .symbol = "M", + .translation = { .factor = 10, .power = 6 } + }); + EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Giga" }), + .symbol = "G", + .translation = { .factor = 10, .power = 9 } + }); + EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Tera" }), + .symbol = "T", + .translation = { .factor = 10, .power = 12 } + }); + EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Peta" }), + .symbol = "P", + .translation = { .factor = 10, .power = 15 } + }); + EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Exa" }), + .symbol = "E", + .translation = { .factor = 10, .power = 18 } + }); + EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zetta" }), + .symbol = "Z", + .translation = { .factor = 10, .power = 21 } + }); + EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yotta" }), + .symbol = "Y", + .translation = { .factor = 10, .power = 24 } + }); + + EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Kibi" }), + .symbol = "Ki", + .translation = { .factor = 1024, .power = 1 } + }); + EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Mebi" }), + .symbol = "Mi", + .translation = { .factor = 1024, .power = 2 } + }); + EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Gibi" }), + .symbol = "Gi", + .translation = { .factor = 1024, .power = 3 } + }); + EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Tebi" }), + .symbol = "Ti", + .translation = { .factor = 1024, .power = 4 } + }); + EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Pebi" }), + .symbol = "Pi", + .translation = { .factor = 1024, .power = 5 } + }); + EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Exbi" }), + .symbol = "Ei", + .translation = { .factor = 1024, .power = 6 } + }); + EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zebi" }), + .symbol = "Zi", + .translation = { .factor = 1024, .power = 7 } + }); + EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yobi" }), + .symbol = "Yi", + .translation = { .factor = 1024, .power = 8 } + }); + + ecs_set_scope(world, prev_scope); + + /* Duration units */ + + EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Duration" }); + prev_scope = ecs_set_scope(world, EcsDuration); + + EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Seconds" }), + .quantity = EcsDuration, + .symbol = "s" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsSeconds, + .kind = EcsF32 + }); + EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "PicoSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsPico }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPicoSeconds, + .kind = EcsF32 + }); + + + EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "NanoSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsNano }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNanoSeconds, + .kind = EcsF32 + }); + + EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MicroSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsMicro }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMicroSeconds, + .kind = EcsF32 + }); + + EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilliSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsMilli }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilliSeconds, + .kind = EcsF32 + }); + + EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Minutes" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .symbol = "min", + .translation = { .factor = 60, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMinutes, + .kind = EcsU32 + }); + + EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hours" }), + .quantity = EcsDuration, + .base = EcsMinutes, + .symbol = "h", + .translation = { .factor = 60, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsHours, + .kind = EcsU32 + }); + + EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Days" }), + .quantity = EcsDuration, + .base = EcsHours, + .symbol = "d", + .translation = { .factor = 24, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDays, + .kind = EcsU32 + }); + ecs_set_scope(world, prev_scope); + + /* Time units */ + + EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Time" }); + prev_scope = ecs_set_scope(world, EcsTime); + + EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Date" }), + .quantity = EcsTime }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDate, + .kind = EcsU32 + }); + ecs_set_scope(world, prev_scope); + + /* Mass units */ + + EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Mass" }); + prev_scope = ecs_set_scope(world, EcsMass); + EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Grams" }), + .quantity = EcsMass, + .symbol = "g" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGrams, + .kind = EcsF32 + }); + EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloGrams" }), + .quantity = EcsMass, + .prefix = EcsKilo, + .base = EcsGrams }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloGrams, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Electric current units */ + + EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "ElectricCurrent" }); + prev_scope = ecs_set_scope(world, EcsElectricCurrent); + EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Ampere" }), + .quantity = EcsElectricCurrent, + .symbol = "A" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsAmpere, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Amount of substance units */ + + EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Amount" }); + prev_scope = ecs_set_scope(world, EcsAmount); + EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Mole" }), + .quantity = EcsAmount, + .symbol = "mol" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMole, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Luminous intensity units */ + + EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "LuminousIntensity" }); + prev_scope = ecs_set_scope(world, EcsLuminousIntensity); + EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Candela" }), + .quantity = EcsLuminousIntensity, + .symbol = "cd" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCandela, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Force units */ + + EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Force" }); + prev_scope = ecs_set_scope(world, EcsForce); + EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Newton" }), + .quantity = EcsForce, + .symbol = "N" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNewton, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Length units */ + + EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Length" }); + prev_scope = ecs_set_scope(world, EcsLength); + EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Meters" }), + .quantity = EcsLength, + .symbol = "m" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMeters, + .kind = EcsF32 + }); + + EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "PicoMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsPico }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPicoMeters, + .kind = EcsF32 + }); + + EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "NanoMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsNano }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNanoMeters, + .kind = EcsF32 + }); + + EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MicroMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsMicro }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMicroMeters, + .kind = EcsF32 + }); + + EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilliMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsMilli }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilliMeters, + .kind = EcsF32 + }); + + EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "CentiMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsCenti }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCentiMeters, + .kind = EcsF32 + }); + + EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMeters, + .kind = EcsF32 + }); + + EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Miles" }), + .quantity = EcsLength, + .symbol = "mi" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMiles, + .kind = EcsF32 + }); + + EcsPixels = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Pixels" }), + .quantity = EcsLength, + .symbol = "px" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPixels, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Pressure units */ + + EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Pressure" }); + prev_scope = ecs_set_scope(world, EcsPressure); + EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Pascal" }), + .quantity = EcsPressure, + .symbol = "Pa" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPascal, + .kind = EcsF32 + }); + EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bar" }), + .quantity = EcsPressure, + .symbol = "bar" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBar, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Speed units */ + + EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Speed" }); + prev_scope = ecs_set_scope(world, EcsSpeed); + EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MetersPerSecond" }), + .quantity = EcsSpeed, + .base = EcsMeters, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMetersPerSecond, + .kind = EcsF32 + }); + EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }), + .quantity = EcsSpeed, + .base = EcsKiloMeters, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMetersPerSecond, + .kind = EcsF32 + }); + EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMetersPerHour" }), + .quantity = EcsSpeed, + .base = EcsKiloMeters, + .over = EcsHours }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMetersPerHour, + .kind = EcsF32 + }); + EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilesPerHour" }), + .quantity = EcsSpeed, + .base = EcsMiles, + .over = EcsHours }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilesPerHour, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Acceleration */ + + EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Acceleration" }), + .base = EcsMetersPerSecond, + .over = EcsSeconds }); + ecs_quantity_init(world, &(ecs_entity_desc_t){ + .id = EcsAcceleration + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsAcceleration, + .kind = EcsF32 + }); + + /* Temperature units */ + + EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Temperature" }); + prev_scope = ecs_set_scope(world, EcsTemperature); + EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Kelvin" }), + .quantity = EcsTemperature, + .symbol = "K" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKelvin, + .kind = EcsF32 + }); + EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Celsius" }), + .quantity = EcsTemperature, + .symbol = "°C" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCelsius, + .kind = EcsF32 + }); + EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Fahrenheit" }), + .quantity = EcsTemperature, + .symbol = "F" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsFahrenheit, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Data units */ + + EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Data" }); + prev_scope = ecs_set_scope(world, EcsData); + + EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bits" }), + .quantity = EcsData, + .symbol = "bit" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBits, + .kind = EcsU64 + }); + + EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBits, + .kind = EcsU64 + }); + + EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsMega }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBits, + .kind = EcsU64 + }); + + EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsGiga }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBits, + .kind = EcsU64 + }); + + EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bytes" }), + .quantity = EcsData, + .symbol = "B", + .base = EcsBits, + .translation = { .factor = 8, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBytes, + .kind = EcsU64 + }); + + EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBytes, + .kind = EcsU64 + }); + + EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsMega }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBytes, + .kind = EcsU64 + }); + + EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsGiga }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBytes, + .kind = EcsU64 + }); + + EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KibiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsKibi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKibiBytes, + .kind = EcsU64 + }); + + EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MebiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsMebi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMebiBytes, + .kind = EcsU64 + }); + + EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GibiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsGibi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGibiBytes, + .kind = EcsU64 + }); + + ecs_set_scope(world, prev_scope); + + /* DataRate units */ + + EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "DataRate" }); + prev_scope = ecs_set_scope(world, EcsDataRate); + + EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "BitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsBits, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBitsPerSecond, + .kind = EcsU64 + }); + + EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsKiloBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBitsPerSecond, + .kind = EcsU64 + }); + + EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsMegaBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBitsPerSecond, + .kind = EcsU64 + }); + + EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsGigaBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBitsPerSecond, + .kind = EcsU64 + }); + + EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "BytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsBytes, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBytesPerSecond, + .kind = EcsU64 + }); + + EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsKiloBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBytesPerSecond, + .kind = EcsU64 + }); + + EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsMegaBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBytesPerSecond, + .kind = EcsU64 + }); + + EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsGigaBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBytesPerSecond, + .kind = EcsU64 + }); + + ecs_set_scope(world, prev_scope); + + /* Percentage */ + + EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Percentage" }); + ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = EcsPercentage, + .symbol = "%" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPercentage, + .kind = EcsF32 + }); + + /* Angles */ + + EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Angle" }); + prev_scope = ecs_set_scope(world, EcsAngle); + EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Radians" }), + .quantity = EcsAngle, + .symbol = "rad" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsRadians, + .kind = EcsF32 + }); + + EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Degrees" }), + .quantity = EcsAngle, + .symbol = "°" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDegrees, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* DeciBel */ + + EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bel" }), + .symbol = "B" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBel, + .kind = EcsF32 + }); + EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "DeciBel" }), + .prefix = EcsDeci, + .base = EcsBel }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDeciBel, + .kind = EcsF32 + }); + + EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Frequency" }); + prev_scope = ecs_set_scope(world, EcsFrequency); + + EcsHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hertz" }), + .quantity = EcsFrequency, + .symbol = "Hz" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsHertz, + .kind = EcsF32 + }); + + EcsKiloHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloHertz" }), + .prefix = EcsKilo, + .base = EcsHertz }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloHertz, + .kind = EcsF32 + }); + + EcsMegaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaHertz" }), + .prefix = EcsMega, + .base = EcsHertz }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaHertz, + .kind = EcsF32 + }); + + EcsGigaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaHertz" }), + .prefix = EcsGiga, + .base = EcsHertz }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaHertz, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Uri" }); + prev_scope = ecs_set_scope(world, EcsUri); + + EcsUriHyperlink = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hyperlink" }), + .quantity = EcsUri }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsUriHyperlink, + .kind = EcsString + }); + + EcsUriImage = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Image" }), + .quantity = EcsUri }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsUriImage, + .kind = EcsString + }); + + EcsUriFile = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "File" }), + .quantity = EcsUri }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsUriFile, + .kind = EcsString + }); + ecs_set_scope(world, prev_scope); + + /* Documentation */ +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); + + ecs_doc_set_brief(world, EcsDuration, + "Time amount (e.g. \"20 seconds\", \"2 hours\")"); + ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); + ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); + ecs_doc_set_brief(world, EcsHours, "60 minutes"); + ecs_doc_set_brief(world, EcsDays, "24 hours"); + + ecs_doc_set_brief(world, EcsTime, + "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); + ecs_doc_set_brief(world, EcsDate, + "Seconds passed since January 1st 1970"); + + ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); + + ecs_doc_set_brief(world, EcsElectricCurrent, + "Units of electrical current (e.g. \"2 ampere\")"); + + ecs_doc_set_brief(world, EcsAmount, + "Units of amount of substance (e.g. \"2 mole\")"); + + ecs_doc_set_brief(world, EcsLuminousIntensity, + "Units of luminous intensity (e.g. \"1 candela\")"); + + ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); + + ecs_doc_set_brief(world, EcsLength, + "Units of length (e.g. \"5 meters\", \"20 miles\")"); + + ecs_doc_set_brief(world, EcsPressure, + "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); + + ecs_doc_set_brief(world, EcsSpeed, + "Units of movement (e.g. \"5 meters/second\")"); + + ecs_doc_set_brief(world, EcsAcceleration, + "Unit of speed increase (e.g. \"5 meters/second/second\")"); + + ecs_doc_set_brief(world, EcsTemperature, + "Units of temperature (e.g. \"5 degrees Celsius\")"); + + ecs_doc_set_brief(world, EcsData, + "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); + + ecs_doc_set_brief(world, EcsDataRate, + "Units of data transmission (e.g. \"100 megabits/second\")"); + + ecs_doc_set_brief(world, EcsAngle, + "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); + + ecs_doc_set_brief(world, EcsFrequency, + "The number of occurrences of a repeating event per unit of time."); + + ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); +#endif +} + +#endif + +/** + * @file datastructures/allocator.c + * @brief Allocator for any size. + * + * Allocators create a block allocator for each requested size. + */ + + +static +ecs_size_t flecs_allocator_size( + ecs_size_t size) +{ + return ECS_ALIGN(size, 16); +} + +static +ecs_size_t flecs_allocator_size_hash( + ecs_size_t size) +{ + return size >> 4; +} + +void flecs_allocator_init( + ecs_allocator_t *a) +{ + flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, + FLECS_SPARSE_PAGE_SIZE); + flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); +} + +void flecs_allocator_fini( + ecs_allocator_t *a) +{ + int32_t i = 0, count = flecs_sparse_count(&a->sizes); + for (i = 0; i < count; i ++) { + ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( + &a->sizes, ecs_block_allocator_t, i); + flecs_ballocator_fini(ba); + } + flecs_sparse_fini(&a->sizes); + flecs_ballocator_fini(&a->chunks); +} + +ecs_block_allocator_t* flecs_allocator_get( + ecs_allocator_t *a, + ecs_size_t size) +{ + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); + if (!size) { + return NULL; + } + + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL); + size = flecs_allocator_size(size); + ecs_size_t hash = flecs_allocator_size_hash(size); + ecs_block_allocator_t *result = flecs_sparse_get_any_t(&a->sizes, + ecs_block_allocator_t, (uint32_t)hash); + + if (!result) { + result = flecs_sparse_ensure_fast_t(&a->sizes, + ecs_block_allocator_t, (uint32_t)hash); + flecs_ballocator_init(result, size); + } + + ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); + + return result; +} + +char* flecs_strdup( + ecs_allocator_t *a, + const char* str) +{ + ecs_size_t len = ecs_os_strlen(str); + char *result = flecs_alloc_n(a, char, len + 1); + ecs_os_memcpy(result, str, len + 1); + return result; +} + +void flecs_strfree( + ecs_allocator_t *a, + char* str) +{ + ecs_size_t len = ecs_os_strlen(str); + flecs_free_n(a, char, len + 1, str); +} + +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src) +{ + ecs_block_allocator_t *ba = flecs_allocator_get(a, size); + if (ba) { + void *dst = flecs_balloc(ba); + ecs_os_memcpy(dst, src, size); + return dst; + } else { + return NULL; + } +} + +/** + * @file datastructures/bitset.c + * @brief Bitset data structure. + * + * Simple bitset implementation. The bitset allows for storage of arbitrary + * numbers of bits. + */ + + +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 flecs_bitset_init( + ecs_bitset_t* bs) +{ + bs->size = 0; + bs->count = 0; + bs->data = NULL; +} + +void flecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count) +{ + if (count > bs->count) { + bs->count = count; + ensure(bs, count); + } +} + +void flecs_bitset_fini( + ecs_bitset_t *bs) +{ + ecs_os_free(bs->data); + bs->data = NULL; + bs->count = 0; +} + +void flecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count) +{ + int32_t elem = bs->count += count; + ensure(bs, elem); +} + +void flecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + uint32_t hi = ((uint32_t)elem) >> 6; + uint32_t lo = ((uint32_t)elem) & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +error: + return; +} + +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +error: + return false; +} + +int32_t flecs_bitset_count( + const ecs_bitset_t *bs) +{ + return bs->count; +} + +void flecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t last = bs->count - 1; + bool last_value = flecs_bitset_get(bs, last); + flecs_bitset_set(bs, elem, last_value); + flecs_bitset_set(bs, last, 0); + bs->count --; +error: + return; +} + +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) +{ + ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); + + bool a = flecs_bitset_get(bs, elem_a); + bool b = flecs_bitset_get(bs, elem_b); + flecs_bitset_set(bs, elem_a, b); + flecs_bitset_set(bs, elem_b, a); +error: + return; +} + +/** + * @file datastructures/block_allocator.c + * @brief Block allocator. + * + * A block allocator is an allocator for a fixed size that allocates blocks of + * memory with N elements of the requested size. + */ + + +// #ifdef FLECS_SANITIZE +// #define FLECS_MEMSET_UNINITIALIZED +// #endif + +int64_t ecs_block_allocator_alloc_count = 0; +int64_t ecs_block_allocator_free_count = 0; + +static +ecs_block_allocator_chunk_header_t* flecs_balloc_block( + ecs_block_allocator_t *allocator) +{ + if (!allocator->chunk_size) { + return NULL; + } + + ecs_block_allocator_block_t *block = + ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + + allocator->block_size); + ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, + ECS_SIZEOF(ecs_block_allocator_block_t)); + + block->memory = first_chunk; + if (!allocator->block_tail) { + ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); + block->next = NULL; + allocator->block_head = block; + allocator->block_tail = block; + } else { + block->next = NULL; + allocator->block_tail->next = block; + allocator->block_tail = block; + } + + ecs_block_allocator_chunk_header_t *chunk = first_chunk; + int32_t i, end; + for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { + chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); + chunk = chunk->next; + } + + ecs_os_linc(&ecs_block_allocator_alloc_count); + + chunk->next = NULL; + return first_chunk; +} + +void flecs_ballocator_init( + ecs_block_allocator_t *ba, + ecs_size_t size) +{ + ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ba->data_size = size; +#ifdef FLECS_SANITIZE + size += ECS_SIZEOF(int64_t); +#endif + ba->chunk_size = ECS_ALIGN(size, 16); + ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1); + ba->block_size = ba->chunks_per_block * ba->chunk_size; + ba->head = NULL; + ba->block_head = NULL; + ba->block_tail = NULL; +} + +ecs_block_allocator_t* flecs_ballocator_new( + ecs_size_t size) +{ + ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); + flecs_ballocator_init(result, size); + return result; +} + +void flecs_ballocator_fini( + ecs_block_allocator_t *ba) +{ + ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + +#ifdef FLECS_SANITIZE + ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, + "(size = %u)", (uint32_t)ba->data_size); +#endif + + ecs_block_allocator_block_t *block; + for (block = ba->block_head; block;) { + ecs_block_allocator_block_t *next = block->next; + ecs_os_free(block); + ecs_os_linc(&ecs_block_allocator_free_count); + block = next; + } + ba->block_head = NULL; +} + +void flecs_ballocator_free( + ecs_block_allocator_t *ba) +{ + flecs_ballocator_fini(ba); + ecs_os_free(ba); +} + +void* flecs_balloc( + ecs_block_allocator_t *ba) +{ + void *result; +#ifdef FLECS_USE_OS_ALLOC + result = ecs_os_malloc(ba->data_size); +#else + + if (!ba) return NULL; + + if (!ba->head) { + ba->head = flecs_balloc_block(ba); + } + + result = ba->head; + ba->head = ba->head->next; + +#ifdef FLECS_SANITIZE + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); + ba->alloc_count ++; + *(int64_t*)result = ba->chunk_size; + result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); +#endif +#endif + +#ifdef FLECS_MEMSET_UNINITIALIZED + ecs_os_memset(result, 0xAA, ba->data_size); +#endif + + return result; +} + +void* flecs_bcalloc( + ecs_block_allocator_t *ba) +{ +#ifdef FLECS_USE_OS_ALLOC + return ecs_os_calloc(ba->data_size); +#endif + + if (!ba) return NULL; + void *result = flecs_balloc(ba); + ecs_os_memset(result, 0, ba->data_size); + return result; +} + +void flecs_bfree( + ecs_block_allocator_t *ba, + void *memory) +{ +#ifdef FLECS_USE_OS_ALLOC + ecs_os_free(memory); + return; +#endif + + if (!ba) { + ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); + return; + } + if (memory == NULL) { + return; + } + +#ifdef FLECS_SANITIZE + memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); + if (*(int64_t*)memory != ba->chunk_size) { + ecs_err("chunk %p returned to wrong allocator " + "(chunk = %ub, allocator = %ub)", + memory, *(int64_t*)memory, ba->chunk_size); + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + ba->alloc_count --; +#endif + + ecs_block_allocator_chunk_header_t *chunk = memory; + chunk->next = ba->head; + ba->head = chunk; + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); +} + +void* flecs_brealloc( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory) +{ + void *result; +#ifdef FLECS_USE_OS_ALLOC + result = ecs_os_realloc(memory, dst->data_size); +#else + if (dst == src) { + return memory; + } + + result = flecs_balloc(dst); + if (result && src) { + ecs_size_t size = src->data_size; + if (dst->data_size < size) { + size = dst->data_size; + } + ecs_os_memcpy(result, memory, size); + } + flecs_bfree(src, memory); +#endif +#ifdef FLECS_MEMSET_UNINITIALIZED + if (dst && src && (dst->data_size > src->data_size)) { + ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, + dst->data_size - src->data_size); + } else if (dst && !src) { + ecs_os_memset(result, 0xAA, dst->data_size); + } +#endif + + return result; +} + +void* flecs_bdup( + ecs_block_allocator_t *ba, + void *memory) +{ +#ifdef FLECS_USE_OS_ALLOC + if (memory && ba->chunk_size) { + return ecs_os_memdup(memory, ba->data_size); + } else { + return NULL; + } +#endif + + void *result = flecs_balloc(ba); + if (result) { + ecs_os_memcpy(result, memory, ba->data_size); + } + return result; +} + +// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) +// main repo: https://github.com/wangyi-fudan/wyhash +// author: 王一 Wang Yi +// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, +// Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, +// Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, +// hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric + +/* quick example: + string s="fjsakfdsjkf"; + uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); +*/ + + +#ifndef WYHASH_CONDOM +//protections that produce different results: +//1: normal valid behavior +//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" +#define WYHASH_CONDOM 1 +#endif + +#ifndef WYHASH_32BIT_MUM +//0: normal version, slow on 32 bit systems +//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function +#define WYHASH_32BIT_MUM 0 +#endif + +//includes +#include +#include +#if defined(_MSC_VER) && defined(_M_X64) + #include + #pragma intrinsic(_umul128) +#endif + +//likely and unlikely macros +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + #define likely_(x) __builtin_expect(x,1) + #define unlikely_(x) __builtin_expect(x,0) +#else + #define likely_(x) (x) + #define unlikely_(x) (x) +#endif + +//128bit multiply function +static inline void wymum_(uint64_t *A, uint64_t *B){ +#if(WYHASH_32BIT_MUM) + uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; + #if(WYHASH_CONDOM>1) + *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; + #else + *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; + #endif +#elif defined(__SIZEOF_INT128__) + __uint128_t r=*A; r*=*B; + #if(WYHASH_CONDOM>1) + *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); + #else + *A=(uint64_t)r; *B=(uint64_t)(r>>64); + #endif +#elif defined(_MSC_VER) && defined(_M_X64) + #if(WYHASH_CONDOM>1) + uint64_t a, b; + a=_umul128(*A,*B,&b); + *A^=a; *B^=b; + #else + *A=_umul128(*A,*B,B); + #endif +#else + uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; + uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; + #if(WYHASH_CONDOM>1) + *A^=lo; *B^=hi; + #else + *A=lo; *B=hi; + #endif +#endif +} + +//multiply and xor mix function, aka MUM +static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } + +//endian macros +#ifndef WYHASH_LITTLE_ENDIAN + #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 1 + #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 0 + #else + #warning could not determine endianness! Falling back to little endian. + #define WYHASH_LITTLE_ENDIAN 1 + #endif +#endif + +//read functions +#if (WYHASH_LITTLE_ENDIAN) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} +#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} +#elif defined(_MSC_VER) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} +#else +static inline uint64_t wyr8_(const uint8_t *p) { + uint64_t v; memcpy(&v, p, 8); + return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); +} +static inline uint64_t wyr4_(const uint8_t *p) { + uint32_t v; memcpy(&v, p, 4); + return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); +} +#endif +static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} + +//wyhash main function +static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ + const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b; + if(likely_(len<=16)){ + if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); } + else if(likely_(len>0)){ a=wyr3_(p,len); b=0;} + else a=b=0; + } + else{ + size_t i=len; + if(unlikely_(i>48)){ + uint64_t see1=seed, see2=seed; + do{ + seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); + see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1); + see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2); + p+=48; i-=48; + }while(likely_(i>48)); + seed^=see1^see2; + } + while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; } + a=wyr8_(p+i-16); b=wyr8_(p+i-8); + } + a^=secret[1]; b^=seed; wymum_(&a,&b); + return wymix_(a^secret[0]^len,b^secret[1]); +} + +//the default secret parameters +static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; + +uint64_t flecs_hash( + const void *data, + ecs_size_t length) +{ + return wyhash(data, flecs_ito(size_t, length), 0, wyp_); +} + +/** + * @file datastructures/hashmap.c + * @brief Hashmap data structure. + * + * The hashmap data structure is built on top of the map data structure. Where + * the map data structure can only work with 64bit key values, the hashmap can + * hash keys of any size, and handles collisions between hashes. + */ + + +static +int32_t flecs_hashmap_find_key( + const ecs_hashmap_t *map, + ecs_vec_t *keys, + ecs_size_t key_size, + const void *key) +{ + int32_t i, count = ecs_vec_count(keys); + void *key_array = ecs_vec_first(keys); + for (i = 0; i < count; i ++) { + void *key_ptr = ECS_OFFSET(key_array, key_size * i); + if (map->compare(key_ptr, key) == 0) { + return i; + } + } + return -1; +} + +void flecs_hashmap_init_( + ecs_hashmap_t *map, + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare, + ecs_allocator_t *allocator) +{ + map->key_size = key_size; + map->value_size = value_size; + map->hash = hash; + map->compare = compare; + flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); + ecs_map_init(&map->impl, allocator); +} + +void flecs_hashmap_fini( + ecs_hashmap_t *map) +{ + ecs_allocator_t *a = map->impl.allocator; + ecs_map_iter_t it = ecs_map_iter(&map->impl); + + while (ecs_map_next(&it)) { + ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); + ecs_vec_fini(a, &bucket->keys, map->key_size); + ecs_vec_fini(a, &bucket->values, map->value_size); +#ifdef FLECS_SANITIZE + flecs_bfree(&map->bucket_allocator, bucket); +#endif + } + + flecs_ballocator_fini(&map->bucket_allocator); + ecs_map_fini(&map->impl); +} + +void flecs_hashmap_copy( + ecs_hashmap_t *dst, + const ecs_hashmap_t *src) +{ + ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); + + flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, + src->compare, src->impl.allocator); + ecs_map_copy(&dst->impl, &src->impl); + + ecs_allocator_t *a = dst->impl.allocator; + ecs_map_iter_t it = ecs_map_iter(&dst->impl); + while (ecs_map_next(&it)) { + ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); + ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; + ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator); + bucket_ptr[0] = dst_bucket; + dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); + dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); + } +} + +void* flecs_hashmap_get_( + const ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, + ecs_hm_bucket_t, hash); + if (!bucket) { + return NULL; + } + + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return NULL; + } + + return ecs_vec_get(&bucket->values, value_size, index); +} + +flecs_hashmap_result_t flecs_hashmap_ensure_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); + ecs_hm_bucket_t *bucket = r[0]; + if (!bucket) { + bucket = r[0] = flecs_bcalloc(&map->bucket_allocator); + } + + ecs_allocator_t *a = map->impl.allocator; + void *value_ptr, *key_ptr; + ecs_vec_t *keys = &bucket->keys; + ecs_vec_t *values = &bucket->values; + if (!keys->array) { + keys = ecs_vec_init(a, &bucket->keys, key_size, 1); + values = ecs_vec_init(a, &bucket->values, value_size, 1); + key_ptr = ecs_vec_append(a, keys, key_size); + value_ptr = ecs_vec_append(a, values, value_size); + ecs_os_memcpy(key_ptr, key, key_size); + ecs_os_memset(value_ptr, 0, value_size); + } else { + int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); + if (index == -1) { + key_ptr = ecs_vec_append(a, keys, key_size); + value_ptr = ecs_vec_append(a, values, value_size); + ecs_os_memcpy(key_ptr, key, key_size); + ecs_os_memset(value_ptr, 0, value_size); + } else { + key_ptr = ecs_vec_get(keys, key_size, index); + value_ptr = ecs_vec_get(values, value_size, index); + } + } + + return (flecs_hashmap_result_t){ + .key = key_ptr, .value = value_ptr, .hash = hash + }; +} + +void flecs_hashmap_set_( + ecs_hashmap_t *map, + ecs_size_t key_size, + void *key, + ecs_size_t value_size, + const void *value) +{ + void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value; + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(value_ptr, value, value_size); +} + +ecs_hm_bucket_t* flecs_hashmap_get_bucket( + const ecs_hashmap_t *map, + uint64_t hash) +{ + ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); +} + +void flecs_hm_bucket_remove( + ecs_hashmap_t *map, + ecs_hm_bucket_t *bucket, + uint64_t hash, + int32_t index) +{ + ecs_vec_remove(&bucket->keys, map->key_size, index); + ecs_vec_remove(&bucket->values, map->value_size, index); + + if (!ecs_vec_count(&bucket->keys)) { + ecs_allocator_t *a = map->impl.allocator; + ecs_vec_fini(a, &bucket->keys, map->key_size); + ecs_vec_fini(a, &bucket->values, map->value_size); + ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); + ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; + flecs_bfree(&map->bucket_allocator, bucket); + } +} + +void flecs_hashmap_remove_w_hash_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size, + uint64_t hash) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + (void)value_size; + + ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, + ecs_hm_bucket_t, hash); + if (!bucket) { + return; + } + + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return; + } + + flecs_hm_bucket_remove(map, bucket, hash, index); +} + +void flecs_hashmap_remove_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); +} + +flecs_hashmap_iter_t flecs_hashmap_iter( + ecs_hashmap_t *map) +{ + return (flecs_hashmap_iter_t){ + .it = ecs_map_iter(&map->impl) + }; +} + +void* flecs_hashmap_next_( + flecs_hashmap_iter_t *it, + ecs_size_t key_size, + void *key_out, + ecs_size_t value_size) +{ + int32_t index = ++ it->index; + ecs_hm_bucket_t *bucket = it->bucket; + while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { + ecs_map_next(&it->it); + bucket = it->bucket = ecs_map_ptr(&it->it); + if (!bucket) { + return NULL; + } + index = it->index = 0; + } + + if (key_out) { + *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); + } + + return ecs_vec_get(&bucket->values, value_size, index); +} + +/** + * @file datastructures/map.c + * @brief Map data structure. + * + * Map data structure for 64bit keys and dynamic payload size. + */ + + +/* The ratio used to determine whether the map should flecs_map_rehash. If + * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ +#define ECS_LOAD_FACTOR (12) +#define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) + +static +uint8_t flecs_log2(uint32_t v) { + static const uint8_t log2table[32] = + {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; +} + +/* Get bucket count for number of elements */ +static +int32_t flecs_map_get_bucket_count( + int32_t count) +{ + return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); +} + +/* Get bucket shift amount for a given bucket count */ +static +uint8_t flecs_map_get_bucket_shift ( + int32_t bucket_count) +{ + return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); +} + +/* Get bucket index for provided map key */ +static +int32_t flecs_map_get_bucket_index( + uint16_t bucket_shift, + ecs_map_key_t key) +{ + ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); + return (int32_t)((11400714819323198485ull * key) >> bucket_shift); +} + +/* Get bucket for key */ +static +ecs_bucket_t* flecs_map_get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t bucket_id = flecs_map_get_bucket_index(map->bucket_shift, key); + ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; +} + +/* Add element to bucket */ +static +ecs_map_val_t* flecs_map_bucket_add( + ecs_block_allocator_t *allocator, + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *new_entry = flecs_balloc(allocator); + new_entry->key = key; + new_entry->next = bucket->first; + bucket->first = new_entry; + return &new_entry->value; +} + +/* Remove element from bucket */ +static +ecs_map_val_t flecs_map_bucket_remove( + ecs_map_t *map, + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + ecs_map_val_t value = entry->value; + ecs_bucket_entry_t **next_holder = &bucket->first; + while(*next_holder != entry) { + next_holder = &(*next_holder)->next; + } + *next_holder = entry->next; + flecs_bfree(map->entry_allocator, entry); + map->count --; + return value; + } + } + + return 0; +} + +/* Free contents of bucket */ +static +void flecs_map_bucket_clear( + ecs_block_allocator_t *allocator, + ecs_bucket_t *bucket) +{ + ecs_bucket_entry_t *entry = bucket->first; + while(entry) { + ecs_bucket_entry_t *next = entry->next; + flecs_bfree(allocator, entry); + entry = next; + } +} + +/* Get payload pointer for key from bucket */ +static +ecs_map_val_t* flecs_map_bucket_get( + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + return &entry->value; + } + } + return NULL; +} + +/* Grow number of buckets */ +static +void flecs_map_rehash( + ecs_map_t *map, + int32_t count) +{ + count = flecs_next_pow_of_2(count); + if (count < 2) { + count = 2; + } + ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); + + int32_t old_count = map->bucket_count; + ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); + + if (map->allocator) { + map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count); + } else { + map->buckets = ecs_os_calloc_n(ecs_bucket_t, count); + } + map->bucket_count = count; + map->bucket_shift = flecs_map_get_bucket_shift(count); + + /* Remap old bucket entries to new buckets */ + for (b = buckets; b < end; b++) { + ecs_bucket_entry_t* entry; + for (entry = b->first; entry;) { + ecs_bucket_entry_t* next = entry->next; + int32_t bucket_index = flecs_map_get_bucket_index( + map->bucket_shift, entry->key); + ecs_bucket_t *bucket = &map->buckets[bucket_index]; + entry->next = bucket->first; + bucket->first = entry; + entry = next; + } + } + + if (map->allocator) { + flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); + } else { + ecs_os_free(buckets); + } +} + +void ecs_map_params_init( + ecs_map_params_t *params, + ecs_allocator_t *allocator) +{ + params->allocator = allocator; + flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); +} + +void ecs_map_params_fini( + ecs_map_params_t *params) +{ + flecs_ballocator_fini(¶ms->entry_allocator); +} + +void ecs_map_init_w_params( + ecs_map_t *result, + ecs_map_params_t *params) +{ + ecs_os_zeromem(result); + + result->allocator = params->allocator; + + if (params->entry_allocator.chunk_size) { + result->entry_allocator = ¶ms->entry_allocator; + result->shared_allocator = true; + } else { + result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); + } + + flecs_map_rehash(result, 0); +} + +void ecs_map_init_w_params_if( + ecs_map_t *result, + ecs_map_params_t *params) +{ + if (!ecs_map_is_init(result)) { + ecs_map_init_w_params(result, params); + } +} + +void ecs_map_init( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + ecs_map_init_w_params(result, &(ecs_map_params_t) { + .allocator = allocator + }); +} + +void ecs_map_init_if( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + if (!ecs_map_is_init(result)) { + ecs_map_init(result, allocator); + } +} + +void ecs_map_fini( + ecs_map_t *map) +{ + if (!ecs_map_is_init(map)) { + return; + } + + bool sanitize = false; +#ifdef FLECS_SANITIZE + sanitize = true; +#endif + + /* Free buckets in sanitized mode, so we can replace the allocator with + * regular malloc/free and use asan/valgrind to find memory errors. */ + ecs_allocator_t *a = map->allocator; + ecs_block_allocator_t *ea = map->entry_allocator; + if (map->shared_allocator || sanitize) { + ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; + while (bucket != end) { + flecs_map_bucket_clear(ea, bucket); + bucket ++; + } + } + + if (ea && !map->shared_allocator) { + flecs_ballocator_free(ea); + map->entry_allocator = NULL; + } + if (a) { + flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets); + } else { + ecs_os_free(map->buckets); + } + + map->bucket_shift = 0; +} + +ecs_map_val_t* ecs_map_get( + const ecs_map_t *map, + ecs_map_key_t key) +{ + return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); +} + +void* ecs_map_get_deref_( + const ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_map_val_t* ptr = flecs_map_bucket_get( + flecs_map_get_bucket(map, key), key); + if (ptr) { + return (void*)(uintptr_t)ptr[0]; + } + return NULL; +} + +void ecs_map_insert( + ecs_map_t *map, + ecs_map_key_t key, + ecs_map_val_t value) +{ + ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); + int32_t map_count = ++map->count; + int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); + int32_t bucket_count = map->bucket_count; + if (tgt_bucket_count > bucket_count) { + flecs_map_rehash(map, tgt_bucket_count); + } + + ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); + flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; +} + +void* ecs_map_insert_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + void *elem = ecs_os_calloc(elem_size); + ecs_map_insert_ptr(map, key, (uintptr_t)elem); + return elem; +} + +ecs_map_val_t* ecs_map_ensure( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); + ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); + if (result) { + return result; + } + + int32_t map_count = ++map->count; + int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); + int32_t bucket_count = map->bucket_count; + if (tgt_bucket_count > bucket_count) { + flecs_map_rehash(map, tgt_bucket_count); + bucket = flecs_map_get_bucket(map, key); + } + + ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); + *v = 0; + return v; +} + +void* ecs_map_ensure_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + ecs_map_val_t *val = ecs_map_ensure(map, key); + if (!*val) { + void *elem = ecs_os_calloc(elem_size); + *val = (ecs_map_val_t)(uintptr_t)elem; + return elem; + } else { + return (void*)(uintptr_t)*val; + } +} + +ecs_map_val_t ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) +{ + return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); +} + +void ecs_map_remove_free( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_map_val_t val = ecs_map_remove(map, key); + if (val) { + ecs_os_free((void*)(uintptr_t)val); + } +} + +void ecs_map_clear( + ecs_map_t *map) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + flecs_map_bucket_clear(map->entry_allocator, &map->buckets[i]); + } + if (map->allocator) { + flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets); + } else { + ecs_os_free(map->buckets); + } + map->buckets = NULL; + map->bucket_count = 0; + map->count = 0; + flecs_map_rehash(map, 2); +} + +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) +{ + if (ecs_map_is_init(map)) { + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .entry = NULL + }; + } else { + return (ecs_map_iter_t){ 0 }; + } +} + +bool ecs_map_next( + ecs_map_iter_t *iter) +{ + const ecs_map_t *map = iter->map; + ecs_bucket_t *end; + if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { + return false; + } + + ecs_bucket_entry_t *entry = NULL; + if (!iter->bucket) { + for (iter->bucket = map->buckets; + iter->bucket != end; + ++iter->bucket) + { + if (iter->bucket->first) { + entry = iter->bucket->first; + break; + } + } + if (iter->bucket == end) { + return false; + } + } else if ((entry = iter->entry) == NULL) { + do { + ++iter->bucket; + if (iter->bucket == end) { + return false; + } + } while(!iter->bucket->first); + entry = iter->bucket->first; + } + + ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); + iter->entry = entry->next; + iter->res = &entry->key; + + return true; +} + +void ecs_map_copy( + ecs_map_t *dst, + const ecs_map_t *src) +{ + if (ecs_map_is_init(dst)) { + ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); + ecs_map_fini(dst); + } + + if (!ecs_map_is_init(src)) { + return; + } + + ecs_map_init(dst, src->allocator); + + ecs_map_iter_t it = ecs_map_iter(src); + while (ecs_map_next(&it)) { + ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); + } +} + +/** + * @file datastructures/name_index.c + * @brief Data structure for resolving 64bit keys by string (name). + */ + + +static +uint64_t flecs_name_index_hash( + const void *ptr) +{ + const ecs_hashed_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; +} + +static +int flecs_name_index_compare( + const void *ptr1, + const void *ptr2) +{ + const ecs_hashed_string_t *str1 = ptr1; + const ecs_hashed_string_t *str2 = ptr2; + ecs_size_t len1 = str1->length; + ecs_size_t len2 = str2->length; + if (len1 != len2) { + return (len1 > len2) - (len1 < len2); + } + + return ecs_os_memcmp(str1->value, str2->value, len1); +} + +void flecs_name_index_init( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator) +{ + flecs_hashmap_init_(hm, + ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), + flecs_name_index_hash, + flecs_name_index_compare, + allocator); +} + +void flecs_name_index_init_if( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator) +{ + if (!hm->compare) { + flecs_name_index_init(hm, allocator); + } +} + +bool flecs_name_index_is_init( + const ecs_hashmap_t *hm) +{ + return hm->compare != NULL; +} + +ecs_hashmap_t* flecs_name_index_new( + ecs_world_t *world, + ecs_allocator_t *allocator) +{ + ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap); + flecs_name_index_init(result, allocator); + result->hashmap_allocator = &world->allocators.hashmap; + return result; +} + +void flecs_name_index_fini( + ecs_hashmap_t *map) +{ + flecs_hashmap_fini(map); +} + +void flecs_name_index_free( + ecs_hashmap_t *map) +{ + if (map) { + flecs_name_index_fini(map); + flecs_bfree(map->hashmap_allocator, map); + } +} + +ecs_hashmap_t* flecs_name_index_copy( + ecs_hashmap_t *map) +{ + ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); + result->hashmap_allocator = map->hashmap_allocator; + flecs_hashmap_copy(result, map); + return result; +} + +ecs_hashed_string_t flecs_get_hashed_string( + const char *name, + ecs_size_t length, + uint64_t hash) +{ + if (!length) { + length = ecs_os_strlen(name); + } else { + ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); + } + + if (!hash) { + hash = flecs_hash(name, length); + } else { + ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); + } + + return (ecs_hashed_string_t) { + .value = ECS_CONST_CAST(char*, name), + .length = length, + .hash = hash + }; +} + +const uint64_t* flecs_name_index_find_ptr( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); + ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); + if (!b) { + return NULL; + } + + ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); + int32_t i, count = ecs_vec_count(&b->keys); + + for (i = 0; i < count; i ++) { + ecs_hashed_string_t *key = &keys[i]; + ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); + + if (hs.length != key->length) { + continue; + } + + if (!ecs_os_strcmp(name, key->value)) { + uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + return e; + } + } + + return NULL; +} + +uint64_t flecs_name_index_find( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); + if (id) { + return id[0]; + } + return 0; +} + +void flecs_name_index_remove( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash) +{ + ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); + if (!b) { + return; + } + + uint64_t *ids = ecs_vec_first(&b->values); + int32_t i, count = ecs_vec_count(&b->values); + for (i = 0; i < count; i ++) { + if (ids[i] == e) { + flecs_hm_bucket_remove(map, b, hash, i); + break; + } + } +} + +void flecs_name_index_update_name( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash, + const char *name) +{ + ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); + if (!b) { + return; + } + + uint64_t *ids = ecs_vec_first(&b->values); + int32_t i, count = ecs_vec_count(&b->values); + for (i = 0; i < count; i ++) { + if (ids[i] == e) { + ecs_hashed_string_t *key = ecs_vec_get_t( + &b->keys, ecs_hashed_string_t, i); + key->value = ECS_CONST_CAST(char*, name); + ecs_assert(ecs_os_strlen(name) == key->length, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_hash(name, key->length) == key->hash, + ECS_INTERNAL_ERROR, NULL); + return; + } + } + + /* Record must already have been in the index */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); +} + +void flecs_name_index_ensure( + ecs_hashmap_t *map, + uint64_t id, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); + + uint64_t existing = flecs_name_index_find( + map, name, key.length, key.hash); + if (existing) { + if (existing != id) { + ecs_abort(ECS_ALREADY_DEFINED, + "conflicting id registered with name '%s'", name); + } + } + + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + map, &key, uint64_t); + *((uint64_t*)hmr.value) = id; +error: + return; +} + +/** + * @file datastructures/sparse.c + * @brief Sparse set data structure. + */ + + +/** Compute the page index from an id by stripping the first 12 bits */ +#define PAGE(index) ((int32_t)((uint32_t)index >> FLECS_SPARSE_PAGE_BITS)) + +/** This computes the offset of an index inside a page */ +#define OFFSET(index) ((int32_t)index & (FLECS_SPARSE_PAGE_SIZE - 1)) + +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + +typedef struct ecs_page_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. */ +} ecs_page_t; + +static +ecs_page_t* flecs_sparse_page_new( + ecs_sparse_t *sparse, + int32_t page_index) +{ + ecs_allocator_t *a = sparse->allocator; + ecs_block_allocator_t *ca = sparse->page_allocator; + int32_t count = ecs_vec_count(&sparse->pages); + ecs_page_t *pages; + + if (count <= page_index) { + ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); + pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); + ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); + } else { + pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); + } + + ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_page_t *result = &pages[page_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 = ca ? flecs_bcalloc(ca) + : ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE); + + /* 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 = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) + : ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); + + ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); + + return result; +} + +static +void flecs_sparse_page_free( + ecs_sparse_t *sparse, + ecs_page_t *page) +{ + ecs_allocator_t *a = sparse->allocator; + ecs_block_allocator_t *ca = sparse->page_allocator; + + if (ca) { + flecs_bfree(ca, page->sparse); + } else { + ecs_os_free(page->sparse); + } + if (a) { + flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); + } else { + ecs_os_free(page->data); + } +} + +static +ecs_page_t* flecs_sparse_get_page( + const ecs_sparse_t *sparse, + int32_t page_index) +{ + ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); + if (page_index >= ecs_vec_count(&sparse->pages)) { + return NULL; + } + return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index); +} + +static +ecs_page_t* flecs_sparse_get_or_create_page( + ecs_sparse_t *sparse, + int32_t page_index) +{ + ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); + if (page && page->sparse) { + return page; + } + + return flecs_sparse_page_new(sparse, page_index); +} + +static +void flecs_sparse_grow_dense( + ecs_sparse_t *sparse) +{ + ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); +} + +static +uint64_t flecs_sparse_strip_generation( + uint64_t *index_out) +{ + uint64_t index = *index_out; + uint64_t gen = index & ECS_GENERATION_MASK; + /* Make sure there's no junk in the id */ + ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), + ECS_INVALID_PARAMETER, NULL); + *index_out -= gen; + return gen; +} + +static +void flecs_sparse_assign_index( + ecs_page_t * page, + 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 .*/ + page->sparse[OFFSET(index)] = dense; + dense_array[dense] = index; +} + +static +uint64_t flecs_sparse_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 flecs_sparse_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; +} + +static +uint64_t flecs_sparse_get_id( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + return sparse->max_id; +} + +static +void flecs_sparse_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 = value; +} + +/* Pair dense id with new sparse id */ +static +uint64_t flecs_sparse_create_id( + ecs_sparse_t *sparse, + int32_t dense) +{ + uint64_t index = flecs_sparse_inc_id(sparse); + flecs_sparse_grow_dense(sparse); + + ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); + ecs_assert(page->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + flecs_sparse_assign_index(page, dense_array, index, dense); + + return index; +} + +/* Create new id */ +static +uint64_t flecs_sparse_new_index( + ecs_sparse_t *sparse) +{ + int32_t dense_count = ecs_vec_count(&sparse->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_vec_first_t(&sparse->dense, uint64_t); + return dense_array[count]; + } else { + return flecs_sparse_create_id(sparse, count); + } +} + +/* 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* flecs_sparse_get_sparse( + const ecs_sparse_t *sparse, + int32_t dense, + uint64_t index) +{ + flecs_sparse_strip_generation(&index); + ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); + if (!page || !page->sparse) { + return NULL; + } + + int32_t offset = OFFSET(index); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; + + return DATA(page->data, sparse->size, offset); +} + +/* Swap dense elements. A swap occurs when an element is removed, or when a + * removed element is recycled. */ +static +void flecs_sparse_swap_dense( + ecs_sparse_t * sparse, + ecs_page_t * page_a, + int32_t a, + int32_t b) +{ + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + uint64_t index_a = dense_array[a]; + uint64_t index_b = dense_array[b]; + + ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, PAGE(index_b)); + flecs_sparse_assign_index(page_a, dense_array, index_a, b); + flecs_sparse_assign_index(page_b, dense_array, index_b, a); +} + +void flecs_sparse_init( + ecs_sparse_t *result, + struct ecs_allocator_t *allocator, + ecs_block_allocator_t *page_allocator, + ecs_size_t size) +{ + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + result->size = size; + result->max_id = UINT64_MAX; + result->allocator = allocator; + result->page_allocator = page_allocator; + + ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0); + ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); + result->dense.count = 1; + + /* 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_vec_first_t(&result->dense, uint64_t)[0] = 0; + + result->count = 1; +} + +void flecs_sparse_clear( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t i, count = ecs_vec_count(&sparse->pages); + ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); + for (i = 0; i < count; i ++) { + int32_t *indices = pages[i].sparse; + if (indices) { + ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); + } + } + + ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); + + sparse->count = 1; + sparse->max_id = 0; +} + +void flecs_sparse_fini( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vec_count(&sparse->pages); + ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); + for (i = 0; i < count; i ++) { + flecs_sparse_page_free(sparse, &pages[i]); + } + + ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); + ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); +} + +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_sparse_new_index(sparse); +} + +void* flecs_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 = flecs_sparse_new_index(sparse); + ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, size, OFFSET(index)); +} + +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + return dense_array[sparse->count - 1]; +} + +void* flecs_sparse_ensure( + 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_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; + + uint64_t gen = flecs_sparse_strip_generation(&index); + ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); + int32_t offset = OFFSET(index); + int32_t dense = page->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 not alive, swap it with the first unused element. */ + flecs_sparse_swap_dense(sparse, page, dense, count); + dense = count; + + /* First unused element is now last used element */ + sparse->count ++; + } else { + /* Dense is already alive, nothing to be done */ + } + + /* Ensure provided generation matches current. Only allow mismatching + * generations if the provided generation count is 0. This allows for + * using the ensure function in combination with ids that have their + * generation stripped. */ +#ifdef FLECS_DEBUG + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); +#endif + } else { + /* Element is not paired yet. Must add a new element to dense array */ + flecs_sparse_grow_dense(sparse); + + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + int32_t dense_count = ecs_vec_count(&sparse->dense) - 1; + int32_t count = sparse->count ++; + + /* If index is larger than max id, update max id */ + if (index >= flecs_sparse_get_id(sparse)) { + flecs_sparse_set_id(sparse, index); + } + + 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]; + ecs_page_t *unused_page = flecs_sparse_get_or_create_page(sparse, PAGE(unused)); + flecs_sparse_assign_index(unused_page, dense_array, unused, dense_count); + } + + flecs_sparse_assign_index(page, dense_array, index, count); + dense_array[count] |= gen; + } + + return DATA(page->data, sparse->size, offset); +} + +void* flecs_sparse_ensure_fast( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index_long) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; + + uint32_t index = (uint32_t)index_long; + ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + int32_t count = sparse->count; + + if (!dense) { + /* Element is not paired yet. Must add a new element to dense array */ + sparse->count = count + 1; + if (count == ecs_vec_count(&sparse->dense)) { + flecs_sparse_grow_dense(sparse); + } + + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + flecs_sparse_assign_index(page, dense_array, index, count); + } + + return DATA(page->data, sparse->size, offset); +} + +void flecs_sparse_remove( + 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; + + ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); + if (!page || !page->sparse) { + return; + } + + uint64_t gen = flecs_sparse_strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + + if (dense) { + uint64_t *dense_array = ecs_vec_first_t(&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; + } + + /* Increase generation */ + dense_array[dense] = index | flecs_sparse_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 */ + flecs_sparse_swap_dense(sparse, page, dense, count - 1); + sparse->count --; + } else { + /* Element is not alive, nothing to be done */ + return; + } + + /* Reset memory to zero on remove */ + void *ptr = DATA(page->data, sparse->size, offset); + ecs_os_memset(ptr, 0, size); + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return; + } +} + +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); + + uint64_t index_w_gen = index; + flecs_sparse_strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + + if (dense) { + /* Increase generation */ + ecs_vec_get_t(&sparse->dense, uint64_t, dense)[0] = index_w_gen; + } else { + /* Element is not paired and thus not alive, nothing to be done */ + } +} + +void* flecs_sparse_get_dense( + 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_vec_first_t(&sparse->dense, uint64_t); + return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]); +} + +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); + if (!page || !page->sparse) { + return false; + } + + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + if (!dense || (dense >= sparse->count)) { + return false; + } + + uint64_t gen = flecs_sparse_strip_generation(&index); + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + + if (cur_gen != gen) { + return false; + } + + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return true; +} + +void* flecs_sparse_try( + const 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; + ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); + if (!page || !page->sparse) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + if (!dense || (dense >= sparse->count)) { + return NULL; + } + + uint64_t gen = flecs_sparse_strip_generation(&index); + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + if (cur_gen != gen) { + return NULL; + } + + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, sparse->size, offset); +} + +void* flecs_sparse_get( + const 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; + + ecs_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_page_t, PAGE(index)); + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t gen = flecs_sparse_strip_generation(&index); + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + (void)cur_gen; (void)gen; + + ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL); + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, sparse->size, offset); +} + +void* flecs_sparse_get_any( + const 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; + + flecs_sparse_strip_generation(&index); + ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); + if (!page || !page->sparse) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, sparse->size, offset); +} + +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) +{ + if (!sparse || !sparse->count) { + return 0; + } + + return sparse->count - 1; +} + +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + if (sparse->dense.array) { + return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); + } else { + return NULL; + } +} + +void ecs_sparse_init( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + flecs_sparse_init(sparse, NULL, NULL, elem_size); +} + +void* ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + return flecs_sparse_add(sparse, elem_size); +} + +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_last_id(sparse); +} + +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_count(sparse); +} + +void* ecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index) +{ + return flecs_sparse_get_dense(sparse, elem_size, index); +} + +void* ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id) +{ + return flecs_sparse_get(sparse, elem_size, id); +} + +/** + * @file datastructures/stack_allocator.c + * @brief Stack allocator. + * + * The stack allocator enables pushing and popping values to a stack, and has + * a lower overhead when compared to block allocators. A stack allocator is a + * good fit for small temporary allocations. + * + * The stack allocator allocates memory in pages. If the requested size of an + * allocation exceeds the page size, a regular allocator is used instead. + */ + + +#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) + +int64_t ecs_stack_allocator_alloc_count = 0; +int64_t ecs_stack_allocator_free_count = 0; + +static +ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { + ecs_stack_page_t *result = ecs_os_malloc( + FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); + result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); + result->next = NULL; + result->id = page_id + 1; + ecs_os_linc(&ecs_stack_allocator_alloc_count); + return result; +} + +void* flecs_stack_alloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) +{ + ecs_stack_page_t *page = stack->tail_page; + if (page == &stack->first && !page->data) { + page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); + ecs_os_linc(&ecs_stack_allocator_alloc_count); + } + + int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); + int16_t next_sp = flecs_ito(int16_t, sp + size); + void *result = NULL; + + if (next_sp > ECS_STACK_PAGE_SIZE) { + if (size > ECS_STACK_PAGE_SIZE) { + result = ecs_os_malloc(size); /* Too large for page */ + goto done; + } + + if (page->next) { + page = page->next; + } else { + page = page->next = flecs_stack_page_new(page->id); + } + sp = 0; + next_sp = flecs_ito(int16_t, size); + stack->tail_page = page; + } + + page->sp = next_sp; + result = ECS_OFFSET(page->data, sp); + +done: +#ifdef FLECS_SANITIZE + ecs_os_memset(result, 0xAA, size); +#endif + return result; +} + +void* flecs_stack_calloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) +{ + void *ptr = flecs_stack_alloc(stack, size, align); + ecs_os_memset(ptr, 0, size); + return ptr; +} + +void flecs_stack_free( + void *ptr, + ecs_size_t size) +{ + if (size > ECS_STACK_PAGE_SIZE) { + ecs_os_free(ptr); + } +} + +ecs_stack_cursor_t* flecs_stack_get_cursor( + ecs_stack_t *stack) +{ + ecs_stack_page_t *page = stack->tail_page; + int16_t sp = stack->tail_page->sp; + ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t); + result->page = page; + result->sp = sp; + result->is_free = false; + +#ifdef FLECS_DEBUG + ++ stack->cursor_count; + result->owner = stack; +#endif + + result->prev = stack->tail_cursor; + stack->tail_cursor = result; + return result; +} + +void flecs_stack_restore_cursor( + ecs_stack_t *stack, + ecs_stack_cursor_t *cursor) +{ + if (!cursor) { + return; + } + + ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, NULL); + ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, NULL); + ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, NULL); + + cursor->is_free = true; + +#ifdef FLECS_DEBUG + -- stack->cursor_count; +#endif + + /* If cursor is not the last on the stack no memory should be freed */ + if (cursor != stack->tail_cursor) { + return; + } + + /* Iterate freed cursors to know how much memory we can free */ + do { + ecs_stack_cursor_t* prev = cursor->prev; + if (!prev || !prev->is_free) { + break; /* Found active cursor, free up until this point */ + } + cursor = prev; + } while (cursor); + + stack->tail_cursor = cursor->prev; + stack->tail_page = cursor->page; + stack->tail_page->sp = cursor->sp; + + /* If the cursor count is zero, stack should be empty + * if the cursor count is non-zero, stack should not be empty */ + ecs_dbg_assert((stack->cursor_count == 0) == + (stack->tail_page == &stack->first && stack->tail_page->sp == 0), + ECS_LEAK_DETECTED, NULL); +} + +void flecs_stack_reset( + ecs_stack_t *stack) +{ + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL); + stack->tail_page = &stack->first; + stack->first.sp = 0; + stack->tail_cursor = NULL; +} + +void flecs_stack_init( + ecs_stack_t *stack) +{ + ecs_os_zeromem(stack); + stack->tail_page = &stack->first; + stack->first.data = NULL; +} + +void flecs_stack_fini( + ecs_stack_t *stack) +{ + ecs_stack_page_t *next, *cur = &stack->first; + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL); + ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, NULL); + ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, NULL); + + do { + next = cur->next; + if (cur == &stack->first) { + if (cur->data) { + ecs_os_linc(&ecs_stack_allocator_free_count); + } + ecs_os_free(cur->data); + } else { + ecs_os_linc(&ecs_stack_allocator_free_count); + ecs_os_free(cur); + } + } while ((cur = next)); +} + +/** + * @file datastructures/strbuf.c + * @brief Utility for constructing strings. + * + * 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. + * + * The functionality provided by strbuf is similar to std::stringstream. + */ + +#include + +/** + * stm32tpl -- STM32 C++ Template Peripheral Library + * Visit https://github.com/antongus/stm32tpl for new versions + * + * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA + */ + +#define MAX_PRECISION (10) +#define EXP_THRESHOLD (3) +#define INT64_MAX_F ((double)INT64_MAX) + +static const double rounders[MAX_PRECISION + 1] = +{ + 0.5, // 0 + 0.05, // 1 + 0.005, // 2 + 0.0005, // 3 + 0.00005, // 4 + 0.000005, // 5 + 0.0000005, // 6 + 0.00000005, // 7 + 0.000000005, // 8 + 0.0000000005, // 9 + 0.00000000005 // 10 +}; + +static +char* flecs_strbuf_itoa( + char *buf, + int64_t v) +{ + char *ptr = buf; + char * p1; + char c; + + if (!v) { + *ptr++ = '0'; + } else { + if (v < 0) { + ptr[0] = '-'; + ptr ++; + v *= -1; + } + + char *p = ptr; + while (v) { + int64_t vdiv = v / 10; + int64_t vmod = v - (vdiv * 10); + p[0] = (char)('0' + vmod); + p ++; + v = vdiv; + } + + p1 = p; + + while (p > ptr) { + c = *--p; + *p = *ptr; + *ptr++ = c; + } + ptr = p1; + } + return ptr; +} + +static +void flecs_strbuf_ftoa( + ecs_strbuf_t *out, + double f, + int precision, + char nan_delim) +{ + char buf[64]; + char * ptr = buf; + char c; + int64_t intPart; + int64_t exp = 0; + + if (ecs_os_isnan(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "NaN"); + ecs_strbuf_appendch(out, nan_delim); + return; + } else { + ecs_strbuf_appendlit(out, "NaN"); + return; + } + } + if (ecs_os_isinf(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "Inf"); + ecs_strbuf_appendch(out, nan_delim); + return; + } else { + ecs_strbuf_appendlit(out, "Inf"); + return; + } + } + + if (precision > MAX_PRECISION) { + precision = MAX_PRECISION; + } + + if (f < 0) { + f = -f; + *ptr++ = '-'; + } + + if (precision < 0) { + if (f < 1.0) precision = 6; + else if (f < 10.0) precision = 5; + else if (f < 100.0) precision = 4; + else if (f < 1000.0) precision = 3; + else if (f < 10000.0) precision = 2; + else if (f < 100000.0) precision = 1; + else precision = 0; + } + + if (precision) { + f += rounders[precision]; + } + + /* Make sure that number can be represented as 64bit int, increase exp */ + while (f > INT64_MAX_F) { + f /= 1000 * 1000 * 1000; + exp += 9; + } + + intPart = (int64_t)f; + f -= (double)intPart; + + ptr = flecs_strbuf_itoa(ptr, intPart); + + if (precision) { + *ptr++ = '.'; + while (precision--) { + f *= 10.0; + c = (char)f; + *ptr++ = (char)('0' + c); + f -= c; + } + } + *ptr = 0; + + /* Remove trailing 0s */ + while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { + ptr[-1] = '\0'; + ptr --; + } + if (ptr != buf && ptr[-1] == '.') { + ptr[-1] = '\0'; + ptr --; + } + + /* If 0s before . exceed threshold, convert to exponent to save space + * without losing precision. */ + char *cur = ptr; + while ((&cur[-1] != buf) && (cur[-1] == '0')) { + cur --; + } + + if (exp || ((ptr - cur) > EXP_THRESHOLD)) { + cur[0] = '\0'; + exp += (ptr - cur); + ptr = cur; + } + + if (exp) { + char *p1 = &buf[1]; + if (nan_delim) { + ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); + buf[0] = nan_delim; + p1 ++; + } + + /* Make sure that exp starts after first character */ + c = p1[0]; + + if (c) { + p1[0] = '.'; + do { + char t = (++p1)[0]; + if (t == '.') { + exp ++; + p1 --; + break; + } + p1[0] = c; + c = t; + exp ++; + } while (c); + ptr = p1 + 1; + } else { + ptr = p1; + } + + ptr[0] = 'e'; + ptr = flecs_strbuf_itoa(ptr + 1, exp); + + if (nan_delim) { + ptr[0] = nan_delim; + ptr ++; + } + + ptr[0] = '\0'; + } + + ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); +} + +/* Add an extra element to the buffer */ +static +void flecs_strbuf_grow( + ecs_strbuf_t *b) +{ + if (!b->content) { + b->content = b->small_string; + b->size = ECS_STRBUF_SMALL_STRING_SIZE; + } else if (b->content == b->small_string) { + b->size *= 2; + b->content = ecs_os_malloc_n(char, b->size); + ecs_os_memcpy(b->content, b->small_string, b->length); + } else { + b->size *= 2; + if (b->size < 16) b->size = 16; + b->content = ecs_os_realloc_n(b->content, char, b->size); + } +} + +static +char* flecs_strbuf_ptr( + ecs_strbuf_t *b) +{ + ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); + return &b->content[b->length]; +} + +/* Append a format string to a buffer */ +static +void flecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* str, + va_list args) +{ + va_list arg_cpy; + + if (!str) { + return; + } + + /* 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 mem_left = b->size - b->length; + int32_t mem_required; + + va_copy(arg_cpy, args); + + if (b->content) { + mem_required = vsnprintf( + flecs_strbuf_ptr(b), + flecs_itosize(mem_left), str, args); + } else { + mem_required = vsnprintf(NULL, 0, str, args); + mem_left = 0; + } + + ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); + + if ((mem_required + 1) >= mem_left) { + while ((mem_required + 1) >= mem_left) { + flecs_strbuf_grow(b); + mem_left = b->size - b->length; + } + vsnprintf(flecs_strbuf_ptr(b), + flecs_itosize(mem_required + 1), str, arg_cpy); + } + + b->length += mem_required; + + va_end(arg_cpy); +} + +static +void flecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str, + int n) +{ + int32_t mem_left = b->size - b->length; + while (n >= mem_left) { + flecs_strbuf_grow(b); + mem_left = b->size - b->length; + } + + ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); + b->length += n; +} + +static +void flecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + if (b->size == b->length) { + flecs_strbuf_grow(b); + } + + flecs_strbuf_ptr(b)[0] = ch; + b->length ++; +} + +void ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_vappend(b, fmt, args); +} + +void ecs_strbuf_append( + ecs_strbuf_t *b, + const char* fmt, + ...) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + + va_list args; + va_start(args, fmt); + flecs_strbuf_vappend(b, fmt, args); + va_end(args); +} + +void ecs_strbuf_appendstrn( + ecs_strbuf_t *b, + const char* str, + int32_t len) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendstr(b, str, len); +} + +void ecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendch(b, ch); +} + +void ecs_strbuf_appendint( + ecs_strbuf_t *b, + int64_t v) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char numbuf[32]; + char *ptr = flecs_strbuf_itoa(numbuf, v); + ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); +} + +void ecs_strbuf_appendflt( + ecs_strbuf_t *b, + double flt, + char nan_delim) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_ftoa(b, flt, 10, nan_delim); +} + +void ecs_strbuf_appendbool( + ecs_strbuf_t *buffer, + bool v) +{ + ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); + if (v) { + ecs_strbuf_appendlit(buffer, "true"); + } else { + ecs_strbuf_appendlit(buffer, "false"); + } +} + +void ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); +} + +void ecs_strbuf_mergebuff( + ecs_strbuf_t *b, + ecs_strbuf_t *src) +{ + if (src->content) { + ecs_strbuf_appendstr(b, src->content); + } + ecs_strbuf_reset(src); +} + +char* ecs_strbuf_get( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char *result = b->content; + if (!result) { + return NULL; + } + + ecs_strbuf_appendch(b, '\0'); + result = b->content; + + if (result == b->small_string) { + result = ecs_os_memdup_n(result, char, b->length + 1); + } + + b->length = 0; + b->content = NULL; + b->size = 0; + b->list_sp = 0; + return result; +} + +char* ecs_strbuf_get_small( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char *result = b->content; + result[b->length] = '\0'; + b->length = 0; + b->content = NULL; + b->size = 0; + return result; +} + +void ecs_strbuf_reset( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + if (b->content && b->content != b->small_string) { + ecs_os_free(b->content); + } + *b = ECS_STRBUF_INIT; +} + +void ecs_strbuf_list_push( + ecs_strbuf_t *b, + const char *list_open, + const char *separator) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, NULL); + b->list_sp ++; + ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, + ECS_INVALID_OPERATION, NULL); + + b->list_stack[b->list_sp].count = 0; + b->list_stack[b->list_sp].separator = separator; + + if (list_open) { + char ch = list_open[0]; + if (ch && !list_open[1]) { + ecs_strbuf_appendch(b, ch); + } else { + ecs_strbuf_appendstr(b, list_open); + } + } +} + +void ecs_strbuf_list_pop( + ecs_strbuf_t *b, + const char *list_close) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, NULL); + + b->list_sp --; + + if (list_close) { + char ch = list_close[0]; + if (ch && !list_close[1]) { + ecs_strbuf_appendch(b, list_close[0]); + } else { + ecs_strbuf_appendstr(b, list_close); + } + } +} + +void ecs_strbuf_list_next( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t list_sp = b->list_sp; + if (b->list_stack[list_sp].count != 0) { + const char *sep = b->list_stack[list_sp].separator; + if (sep && !sep[1]) { + ecs_strbuf_appendch(b, sep[0]); + } else { + ecs_strbuf_appendstr(b, sep); + } + } + b->list_stack[list_sp].count ++; +} + +void ecs_strbuf_list_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_list_next(b); + flecs_strbuf_appendch(b, ch); +} + +void ecs_strbuf_list_append( + ecs_strbuf_t *b, + const char *fmt, + ...) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + + va_list args; + va_start(args, fmt); + flecs_strbuf_vappend(b, fmt, args); + va_end(args); +} + +void ecs_strbuf_list_appendstr( + ecs_strbuf_t *b, + const char *str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + ecs_strbuf_appendstr(b, str); +} + +void ecs_strbuf_list_appendstrn( + ecs_strbuf_t *b, + const char *str, + int32_t n) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + ecs_strbuf_appendstrn(b, str, n); +} + +int32_t ecs_strbuf_written( + const ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return b->length; +} + +/** + * @file datastructures/switch_list.c + * @brief Interleaved linked list for storing mutually exclusive values. + * + * Data structure 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 data structure 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. + * + * The switch list is used to store union relationships. + */ + + +#ifdef FLECS_SANITIZE +static +void flecs_switch_verify_nodes( + ecs_switch_header_t *hdr, + ecs_switch_node_t *nodes) +{ + if (!hdr) { + return; + } + + int32_t prev = -1, elem = hdr->element, count = 0; + while (elem != -1) { + ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); + prev = elem; + elem = nodes[elem].next; + count ++; + } + + ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); +} +#else +#define flecs_switch_verify_nodes(hdr, nodes) +#endif + +static +ecs_switch_header_t* flecs_switch_get_header( + const ecs_switch_t *sw, + uint64_t value) +{ + if (value == 0) { + return NULL; + } + return (ecs_switch_header_t*)ecs_map_get(&sw->hdrs, value); +} + +static +ecs_switch_header_t *flecs_switch_ensure_header( + ecs_switch_t *sw, + uint64_t value) +{ + ecs_switch_header_t *node = flecs_switch_get_header(sw, value); + if (!node && (value != 0)) { + node = (ecs_switch_header_t*)ecs_map_ensure(&sw->hdrs, value); + node->count = 0; + node->element = -1; + } + + return node; +} + +static +void flecs_switch_remove_node( + ecs_switch_header_t *hdr, + ecs_switch_node_t *nodes, + ecs_switch_node_t *node, + int32_t element) +{ + ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); + + /* Update previous node/header */ + 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; + } + + /* Update next node */ + 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); +} + +void flecs_switch_init( + ecs_switch_t *sw, + ecs_allocator_t *allocator, + int32_t elements) +{ + ecs_map_init(&sw->hdrs, allocator); + ecs_vec_init_t(allocator, &sw->nodes, ecs_switch_node_t, elements); + ecs_vec_init_t(allocator, &sw->values, uint64_t, elements); + + ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); + uint64_t *values = ecs_vec_first(&sw->values); + + int i; + for (i = 0; i < elements; i ++) { + nodes[i].prev = -1; + nodes[i].next = -1; + values[i] = 0; + } +} + +void flecs_switch_clear( + ecs_switch_t *sw) +{ + ecs_map_clear(&sw->hdrs); + ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); + ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); +} + +void flecs_switch_fini( + ecs_switch_t *sw) +{ + ecs_map_fini(&sw->hdrs); + ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); + ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); +} + +void flecs_switch_add( + ecs_switch_t *sw) +{ + ecs_switch_node_t *node = ecs_vec_append_t(sw->hdrs.allocator, + &sw->nodes, ecs_switch_node_t); + uint64_t *value = ecs_vec_append_t(sw->hdrs.allocator, + &sw->values, uint64_t); + node->prev = -1; + node->next = -1; + *value = 0; +} + +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vec_count(&sw->nodes); + if (old_count == count) { + return; + } + + ecs_vec_set_count_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t, count); + ecs_vec_set_count_t(sw->hdrs.allocator, &sw->values, uint64_t, count); + + ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); + uint64_t *values = ecs_vec_first(&sw->values); + + 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; + } +} + +int32_t flecs_switch_count( + ecs_switch_t *sw) +{ + ecs_assert(ecs_vec_count(&sw->values) == ecs_vec_count(&sw->nodes), + ECS_INTERNAL_ERROR, NULL); + return ecs_vec_count(&sw->values); +} + +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vec_count(&sw->nodes); + if (old_count >= count) { + return; + } + + flecs_switch_set_count(sw, count); +} + +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vec_count(&sw->nodes); + flecs_switch_set_count(sw, old_count + count); +} + +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vec_first(&sw->values); + 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_vec_first(&sw->nodes); + ecs_switch_node_t *node = &nodes[element]; + + ecs_switch_header_t *dst_hdr = flecs_switch_ensure_header(sw, value); + ecs_switch_header_t *cur_hdr = flecs_switch_get_header(sw, cur_value); + + flecs_switch_verify_nodes(cur_hdr, nodes); + flecs_switch_verify_nodes(dst_hdr, nodes); + + /* 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) { + flecs_switch_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 flecs_switch_remove( + ecs_switch_t *sw, + int32_t elem) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vec_first(&sw->values); + uint64_t value = values[elem]; + ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); + ecs_switch_node_t *node = &nodes[elem]; + + /* If node is currently assigned to a case, remove it from the list */ + if (value != 0) { + ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_switch_verify_nodes(hdr, nodes); + flecs_switch_remove_node(hdr, nodes, node, elem); + } + + int32_t last_elem = ecs_vec_count(&sw->nodes) - 1; + if (last_elem != elem) { + ecs_switch_node_t *last = ecs_vec_last_t(&sw->nodes, ecs_switch_node_t); + int32_t next = last->next, prev = last->prev; + if (next != -1) { + ecs_switch_node_t *n = &nodes[next]; + n->prev = elem; + } + + if (prev != -1) { + ecs_switch_node_t *n = &nodes[prev]; + n->next = elem; + } else { + ecs_switch_header_t *hdr = flecs_switch_get_header(sw, values[last_elem]); + if (hdr && hdr->element != -1) { + ecs_assert(hdr->element == last_elem, + ECS_INTERNAL_ERROR, NULL); + hdr->element = elem; + } + } + } + + /* Remove element from arrays */ + ecs_vec_remove_t(&sw->nodes, ecs_switch_node_t, elem); + ecs_vec_remove_t(&sw->values, uint64_t, elem); +} + +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vec_first(&sw->values); + return values[element]; +} + +ecs_vec_t* flecs_switch_values( + const ecs_switch_t *sw) +{ + return ECS_CONST_CAST(ecs_vec_t*, &sw->values); +} + +int32_t flecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); + if (!hdr) { + return 0; + } + + return hdr->count; +} + +void flecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2) +{ + uint64_t v1 = flecs_switch_get(sw, elem_1); + uint64_t v2 = flecs_switch_get(sw, elem_2); + + flecs_switch_set(sw, elem_2, v1); + flecs_switch_set(sw, elem_1, v2); +} + +int32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); + if (!hdr) { + return -1; + } + + return hdr->element; +} + +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); + + return nodes[element].next; +} + +/** + * @file datastructures/vec.c + * @brief Vector with allocator support. + */ + + +ecs_vec_t* ecs_vec_init( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + v->array = NULL; + v->count = 0; + if (elem_count) { + if (allocator) { + v->array = flecs_alloc(allocator, size * elem_count); + } else { + v->array = ecs_os_malloc(size * elem_count); + } + } + v->size = elem_count; +#ifdef FLECS_SANITIZE + v->elem_size = size; +#endif + return v; +} + +void ecs_vec_init_if( + ecs_vec_t *vec, + ecs_size_t size) +{ + ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); + (void)vec; + (void)size; +#ifdef FLECS_SANITIZE + if (!vec->elem_size) { + ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL); + vec->elem_size = size; + } +#endif +} + +void ecs_vec_fini( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + if (v->array) { + ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + if (allocator) { + flecs_free(allocator, size * v->size, v->array); + } else { + ecs_os_free(v->array); + } + v->array = NULL; + v->count = 0; + v->size = 0; + } +} + +ecs_vec_t* ecs_vec_reset( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + if (!v->size) { + ecs_vec_init(allocator, v, size, 0); + } else { + ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_vec_clear(v); + } + return v; +} + +void ecs_vec_clear( + ecs_vec_t *vec) +{ + vec->count = 0; +} + +ecs_vec_t ecs_vec_copy( + ecs_allocator_t *allocator, + const ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + void *array; + if (allocator) { + array = flecs_dup(allocator, size * v->size, v->array); + } else { + array = ecs_os_memdup(v->array, size * v->size); + } + return (ecs_vec_t) { + .count = v->count, + .size = v->size, + .array = array +#ifdef FLECS_SANITIZE + , .elem_size = size +#endif + }; +} + +ecs_vec_t ecs_vec_copy_shrink( + ecs_allocator_t *allocator, + const ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + int32_t count = v->count; + void *array = NULL; + if (count) { + if (allocator) { + array = flecs_dup(allocator, size * count, v->array); + } else { + array = ecs_os_memdup(v->array, size * count); + } + } + return (ecs_vec_t) { + .count = count, + .size = count, + .array = array +#ifdef FLECS_SANITIZE + , .elem_size = size +#endif + }; +} + +void ecs_vec_reclaim( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + int32_t count = v->count; + if (count < v->size) { + if (count) { + if (allocator) { + v->array = flecs_realloc( + allocator, size * count, size * v->size, v->array); + } else { + v->array = ecs_os_realloc(v->array, size * count); + } + v->size = count; + } else { + ecs_vec_fini(allocator, v, size); + } + } +} + +void ecs_vec_set_size( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + if (v->size != elem_count) { + if (elem_count < v->count) { + elem_count = v->count; + } + + elem_count = flecs_next_pow_of_2(elem_count); + if (elem_count < 2) { + elem_count = 2; + } + if (elem_count != v->size) { + if (allocator) { + v->array = flecs_realloc( + allocator, size * elem_count, size * v->size, v->array); + } else { + v->array = ecs_os_realloc(v->array, size * elem_count); + } + v->size = elem_count; + } + } +} + +void ecs_vec_set_min_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) +{ + if (elem_count > vec->size) { + ecs_vec_set_size(allocator, vec, size, elem_count); + } +} + +void ecs_vec_set_min_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) +{ + ecs_vec_set_min_size(allocator, vec, size, elem_count); + if (vec->count < elem_count) { + vec->count = elem_count; + } +} + +void ecs_vec_set_min_count_zeromem( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) +{ + int32_t count = vec->count; + if (count < elem_count) { + ecs_vec_set_min_count(allocator, vec, size, elem_count); + ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, + size * (elem_count - count)); + } +} + +void ecs_vec_set_count( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + if (v->count != elem_count) { + if (v->size < elem_count) { + ecs_vec_set_size(allocator, v, size, elem_count); + } + + v->count = elem_count; + } +} + +void* ecs_vec_grow( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t count = v->count; + ecs_vec_set_count(allocator, v, size, count + elem_count); + return ECS_ELEM(v->array, size, count); +} + +void* ecs_vec_append( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + int32_t count = v->count; + if (v->size == count) { + ecs_vec_set_size(allocator, v, size, count + 1); + } + v->count = count + 1; + return ECS_ELEM(v->array, size, count); +} + +void ecs_vec_remove( + ecs_vec_t *v, + ecs_size_t size, + int32_t index) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); + if (index == --v->count) { + return; + } + + ecs_os_memcpy( + ECS_ELEM(v->array, size, index), + ECS_ELEM(v->array, size, v->count), + size); +} + +void ecs_vec_remove_last( + ecs_vec_t *v) +{ + v->count --; +} + +int32_t ecs_vec_count( + const ecs_vec_t *v) +{ + return v->count; +} + +int32_t ecs_vec_size( + const ecs_vec_t *v) +{ + return v->size; +} + +void* ecs_vec_get( + const ecs_vec_t *v, + ecs_size_t size, + int32_t index) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); + return ECS_ELEM(v->array, size, index); +} + +void* ecs_vec_last( + const ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(!v->elem_size || size == v->elem_size, + ECS_INVALID_PARAMETER, NULL); + return ECS_ELEM(v->array, size, v->count - 1); +} + +void* ecs_vec_first( + const ecs_vec_t *v) +{ + return v->array; +} + + +static +ecs_entity_index_page_t* flecs_entity_index_ensure_page( + ecs_entity_index_t *index, + uint32_t id) +{ + int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); + if (page_index >= ecs_vec_count(&index->pages)) { + ecs_vec_set_min_count_zeromem_t(index->allocator, &index->pages, + ecs_entity_index_page_t*, page_index + 1); + } + + ecs_entity_index_page_t **page_ptr = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index); + ecs_entity_index_page_t *page = *page_ptr; + if (!page) { + page = *page_ptr = flecs_bcalloc(&index->page_allocator); + ecs_assert(page != NULL, ECS_OUT_OF_MEMORY, NULL); + } + + return page; +} + +void flecs_entity_index_init( + ecs_allocator_t *allocator, + ecs_entity_index_t *index) +{ + index->allocator = allocator; + index->alive_count = 1; + ecs_vec_init_t(allocator, &index->dense, uint64_t, 1); + ecs_vec_set_count_t(allocator, &index->dense, uint64_t, 1); + ecs_vec_init_t(allocator, &index->pages, ecs_entity_index_page_t*, 0); + flecs_ballocator_init(&index->page_allocator, + ECS_SIZEOF(ecs_entity_index_page_t)); +} + +void flecs_entity_index_fini( + ecs_entity_index_t *index) +{ + ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); +#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) + int32_t i, count = ecs_vec_count(&index->pages); + ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); + for (i = 0; i < count; i ++) { + flecs_bfree(&index->page_allocator, pages[i]); + } +#endif + ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); + flecs_ballocator_fini(&index->page_allocator); +} + +ecs_record_t* flecs_entity_index_get_any( + const ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); + ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index)[0]; + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + ecs_assert(r->dense != 0, ECS_INVALID_PARAMETER, NULL); + return r; +} + +ecs_record_t* flecs_entity_index_get( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_get_any(index, entity); + ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, + ECS_INVALID_PARAMETER, NULL); + return r; +} + +ecs_record_t* flecs_entity_index_try_get_any( + const ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); + if (page_index >= ecs_vec_count(&index->pages)) { + return NULL; + } + + ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index)[0]; + if (!page) { + return NULL; + } + + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + if (!r->dense) { + return NULL; + } + + return r; +} + +ecs_record_t* flecs_entity_index_try_get( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + if (r->dense >= index->alive_count) { + return NULL; + } + if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) { + return NULL; + } + } + return r; +} + +ecs_record_t* flecs_entity_index_ensure( + ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + + int32_t dense = r->dense; + if (dense) { + /* Entity is already alive, nothing to be done */ + if (dense < index->alive_count) { + ecs_assert( + ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity, + ECS_INTERNAL_ERROR, NULL); + return r; + } + } else { + /* Entity doesn't have a dense index yet */ + ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity; + r->dense = dense = ecs_vec_count(&index->dense) - 1; + index->max_id = id > index->max_id ? id : index->max_id; + } + + ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); + + /* Entity is not alive, swap with first not alive element */ + uint64_t *ids = ecs_vec_first(&index->dense); + uint64_t e_swap = ids[index->alive_count]; + ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); + ecs_assert(r_swap->dense == index->alive_count, + ECS_INTERNAL_ERROR, NULL); + + r_swap->dense = dense; + r->dense = index->alive_count; + ids[dense] = e_swap; + ids[index->alive_count ++] = entity; + + ecs_assert(flecs_entity_index_is_alive(index, entity), + ECS_INTERNAL_ERROR, NULL); + + return r; +} + +void flecs_entity_index_remove( + ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get(index, entity); + if (!r) { + /* Entity is not alive or doesn't exist, nothing to be done */ + return; + } + + int32_t dense = r->dense; + int32_t i_swap = -- index->alive_count; + uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); + uint64_t e_swap = e_swap_ptr[0]; + ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); + ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); + + r_swap->dense = dense; + r->table = NULL; + r->idr = NULL; + r->row = 0; + r->dense = i_swap; + ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = e_swap; + e_swap_ptr[0] = ECS_GENERATION_INC(entity); + ecs_assert(!flecs_entity_index_is_alive(index, entity), + ECS_INTERNAL_ERROR, NULL); +} + +void flecs_entity_index_set_generation( + ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] = entity; + } +} + +uint64_t flecs_entity_index_get_generation( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; + } else { + return 0; + } +} + +bool flecs_entity_index_is_alive( + const ecs_entity_index_t *index, + uint64_t entity) +{ + return flecs_entity_index_try_get(index, entity) != NULL; +} + +bool flecs_entity_index_is_valid( + const ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + ecs_record_t *r = flecs_entity_index_try_get_any(index, id); + if (!r || !r->dense) { + /* Doesn't exist yet, so is valid */ + return true; + } + + /* If the id exists, it must be alive */ + return r->dense < index->alive_count; +} + +bool flecs_entity_index_exists( + const ecs_entity_index_t *index, + uint64_t entity) +{ + return flecs_entity_index_try_get_any(index, entity) != NULL; +} + +uint64_t flecs_entity_index_new_id( + ecs_entity_index_t *index) +{ + if (index->alive_count != ecs_vec_count(&index->dense)) { + /* Recycle id */ + return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; + } + + /* Create new id */ + uint32_t id = (uint32_t)++ index->max_id; + ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; + + ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + r->dense = index->alive_count ++; + ecs_assert(index->alive_count == ecs_vec_count(&index->dense), + ECS_INTERNAL_ERROR, NULL); + + return id; +} + +uint64_t* flecs_entity_index_new_ids( + ecs_entity_index_t *index, + int32_t count) +{ + int32_t alive_count = index->alive_count; + int32_t new_count = alive_count + count; + int32_t dense_count = ecs_vec_count(&index->dense); + + if (new_count < dense_count) { + /* Recycle ids */ + index->alive_count = new_count; + return ecs_vec_get_t(&index->dense, uint64_t, alive_count); + } + + /* Allocate new ids */ + ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count); + int32_t i, to_add = new_count - dense_count; + for (i = 0; i < to_add; i ++) { + uint32_t id = (uint32_t)++ index->max_id; + int32_t dense = dense_count + i; + ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = id; + ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + r->dense = dense; + } + + index->alive_count = new_count; + return ecs_vec_get_t(&index->dense, uint64_t, alive_count); +} + +void flecs_entity_index_set_size( + ecs_entity_index_t *index, + int32_t size) +{ + ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); +} + +int32_t flecs_entity_index_count( + const ecs_entity_index_t *index) +{ + return index->alive_count - 1; +} + +int32_t flecs_entity_index_size( + const ecs_entity_index_t *index) +{ + return ecs_vec_count(&index->dense) - 1; +} + +int32_t flecs_entity_index_not_alive_count( + const ecs_entity_index_t *index) +{ + return ecs_vec_count(&index->dense) - index->alive_count; +} + +void flecs_entity_index_clear( + ecs_entity_index_t *index) +{ + int32_t i, count = ecs_vec_count(&index->pages); + ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages, + ecs_entity_index_page_t*); + for (i = 0; i < count; i ++) { + ecs_entity_index_page_t *page = pages[i]; + if (page) { + ecs_os_zeromem(page); + } + } + + ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); + + index->alive_count = 1; + index->max_id = 0; +} + +const uint64_t* flecs_entity_index_ids( + const ecs_entity_index_t *index) +{ + return ecs_vec_get_t(&index->dense, uint64_t, 1); +} + +static +void flecs_entity_index_copy_intern( + ecs_entity_index_t * dst, + const ecs_entity_index_t * src) +{ + flecs_entity_index_set_size(dst, flecs_entity_index_size(src)); + const uint64_t *ids = flecs_entity_index_ids(src); + + int32_t i, count = src->alive_count; + for (i = 0; i < count - 1; i ++) { + uint64_t id = ids[i]; + ecs_record_t *src_ptr = flecs_entity_index_get(src, id); + ecs_record_t *dst_ptr = flecs_entity_index_ensure(dst, id); + flecs_entity_index_set_generation(dst, id); + ecs_os_memcpy_t(dst_ptr, src_ptr, ecs_record_t); + } + + dst->max_id = src->max_id; + + ecs_assert(src->alive_count == dst->alive_count, ECS_INTERNAL_ERROR, NULL); +} + +void flecs_entity_index_copy( + ecs_entity_index_t *dst, + const ecs_entity_index_t *src) +{ + if (!src) { + return; + } + + flecs_entity_index_init(src->allocator, dst); + flecs_entity_index_copy_intern(dst, src); +} + +void flecs_entity_index_restore( + ecs_entity_index_t *dst, + const ecs_entity_index_t *src) +{ + if (!src) { + return; + } + + flecs_entity_index_clear(dst); + flecs_entity_index_copy_intern(dst, src); +} + +/** + * @file id_index.c + * @brief Index for looking up tables by (component) id. + * + * An id record stores the administration for an in use (component) id, that is + * an id that has been used in tables. + * + * An id record contains a table cache, which stores the list of tables that + * have the id. Each entry in the cache (a table record) stores the first + * occurrence of the id in the table and the number of occurrences of the id in + * the table (in the case of wildcard ids). + * + * Id records are used in lots of scenarios, like uncached queries, or for + * getting a component array/component for an entity. + */ + + +static +ecs_id_record_elem_t* flecs_id_record_elem( + ecs_id_record_t *head, + ecs_id_record_elem_t *list, + ecs_id_record_t *idr) +{ + return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); +} + +static +void flecs_id_record_elem_insert( + ecs_id_record_t *head, + ecs_id_record_t *idr, + ecs_id_record_elem_t *elem) +{ + ecs_id_record_elem_t *head_elem = flecs_id_record_elem(idr, elem, head); + ecs_id_record_t *cur = head_elem->next; + elem->next = cur; + elem->prev = head; + if (cur) { + ecs_id_record_elem_t *cur_elem = flecs_id_record_elem(idr, elem, cur); + cur_elem->prev = idr; + } + head_elem->next = idr; +} + +static +void flecs_id_record_elem_remove( + ecs_id_record_t *idr, + ecs_id_record_elem_t *elem) +{ + ecs_id_record_t *prev = elem->prev; + ecs_id_record_t *next = elem->next; + ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_record_elem_t *prev_elem = flecs_id_record_elem(idr, elem, prev); + prev_elem->next = next; + if (next) { + ecs_id_record_elem_t *next_elem = flecs_id_record_elem(idr, elem, next); + next_elem->prev = prev; + } +} + +static +void flecs_insert_id_elem( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_id_t wildcard, + ecs_id_record_t *widr) +{ + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + if (!widr) { + widr = flecs_id_record_ensure(world, wildcard); + } + ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_id_record_elem_insert(widr, idr, &idr->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_id_record_elem_insert(widr, idr, &idr->second); + + if (idr->flags & EcsIdTraversable) { + flecs_id_record_elem_insert(widr, idr, &idr->trav); + } + } +} + +static +void flecs_remove_id_elem( + ecs_id_record_t *idr, + ecs_id_t wildcard) +{ + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_id_record_elem_remove(idr, &idr->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_id_record_elem_remove(idr, &idr->second); + + if (idr->flags & EcsIdTraversable) { + flecs_id_record_elem_remove(idr, &idr->trav); + } + } +} + +static +ecs_id_t flecs_id_record_hash( + ecs_id_t id) +{ + id = ecs_strip_generation(id); + if (ECS_IS_PAIR(id)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); + if (r == EcsAny) { + r = EcsWildcard; + } + if (o == EcsAny) { + o = EcsWildcard; + } + id = ecs_pair(r, o); + } + return id; +} + +static +ecs_id_record_t* flecs_id_record_new( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr, *idr_t = NULL; + ecs_id_t hash = flecs_id_record_hash(id); + if (hash >= FLECS_HI_ID_RECORD_ID) { + idr = flecs_bcalloc(&world->allocators.id_record); + ecs_map_insert_ptr(&world->id_index_hi, hash, idr); + } else { + idr = &world->id_index_lo[hash]; + ecs_os_zeromem(idr); + } + + ecs_table_cache_init(world, &idr->cache); + + idr->id = id; + idr->refcount = 1; + idr->reachable.current = -1; + + bool is_wildcard = ecs_id_is_wildcard(id); + bool is_pair = ECS_IS_PAIR(id); + + ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; + if (is_pair) { + // rel = ecs_pair_first(world, id); + rel = ECS_PAIR_FIRST(id); + rel = flecs_entities_get_generation(world, rel); + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + + /* Relationship object can be 0, as tables without a ChildOf + * relationship are added to the (ChildOf, 0) id record */ + tgt = ECS_PAIR_SECOND(id); + +#ifdef FLECS_DEBUG + /* Check constraints */ + if (tgt) { + tgt = flecs_entities_get_generation(world, tgt); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + } + if (tgt && !ecs_id_is_wildcard(tgt)) { + /* Check if target of relationship satisfies OneOf property */ + ecs_entity_t oneof = flecs_get_oneof(world, rel); + ecs_check( !oneof || ecs_has_pair(world, tgt, EcsChildOf, oneof), + ECS_CONSTRAINT_VIOLATED, NULL); + (void)oneof; + + /* Check if we're not trying to inherit from a final target */ + if (rel == EcsIsA) { + bool is_final = ecs_has_id(world, tgt, EcsFinal); + ecs_check(!is_final, ECS_CONSTRAINT_VIOLATED, + "cannot inherit from final entity"); + (void)is_final; + } + } +#endif + + if (!is_wildcard && (rel != EcsFlag)) { + /* Inherit flags from (relationship, *) record */ + ecs_id_record_t *idr_r = flecs_id_record_ensure( + world, ecs_pair(rel, EcsWildcard)); + idr->parent = idr_r; + idr->flags = idr_r->flags; + + /* If pair is not a wildcard, append it to wildcard lists. These + * allow for quickly enumerating all relationships for an object, + * or all objects for a relationship. */ + flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); + + idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); + flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); + + if (rel == EcsUnion) { + idr->flags |= EcsIdUnion; + } + } + } else { + rel = id & ECS_COMPONENT_MASK; + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + } + + /* Initialize type info if id is not a tag */ + if (!is_wildcard && (!role || is_pair)) { + if (!(idr->flags & EcsIdTag)) { + const ecs_type_info_t *ti = flecs_type_info_get(world, rel); + if (!ti && tgt) { + ti = flecs_type_info_get(world, tgt); + } + idr->type_info = ti; + } + } + + /* Mark entities that are used as component/pair ids. When a tracked + * entity is deleted, cleanup policies are applied so that the store + * won't contain any tables with deleted ids. */ + + /* Flag for OnDelete policies */ + flecs_add_flag(world, rel, EcsEntityIsId); + if (tgt) { + /* Flag for OnDeleteTarget policies */ + ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt); + ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(tgt_r, EcsEntityIsTarget); + if (idr->flags & EcsIdTraversable) { + /* Flag used to determine if object should be traversed when + * propagating events or with super/subset queries */ + flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); + + /* Add reference to (*, tgt) id record to entity record */ + tgt_r->idr = idr_t; + } + } + + ecs_observable_t *o = &world->observable; + idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; + idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; + idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; + idr->flags |= flecs_observers_exist(o, id, EcsUnSet) * EcsIdHasUnSet; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; + + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); + ecs_os_free(id_str); + } + + /* Update counters */ + world->info.id_create_total ++; + world->info.component_id_count += idr->type_info != NULL; + world->info.tag_id_count += idr->type_info == NULL; + world->info.pair_id_count += is_pair; + + return idr; +#ifdef FLECS_DEBUG +error: + return NULL; +#endif +} + +static +void flecs_id_record_assert_empty( + ecs_id_record_t *idr) +{ + (void)idr; + ecs_assert(flecs_table_cache_count(&idr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); +} + +static +void flecs_id_record_free( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = idr->id; + + flecs_id_record_assert_empty(idr); + + /* Id is still in use by a filter, query, rule or observer */ + ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), + ECS_ID_IN_USE, "cannot delete id that is queried for"); + + if (ECS_IS_PAIR(id)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if (!ecs_id_is_wildcard(id)) { + if (ECS_PAIR_FIRST(id) != EcsFlag) { + /* If id is not a wildcard, remove it from the wildcard lists */ + flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard)); + flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, tgt)); + } + } else { + ecs_log_push_2(); + + /* If id is a wildcard, it means that all id records that match the + * wildcard are also empty, so release them */ + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + /* Iterate (*, Target) list */ + ecs_id_record_t *cur, *next = idr->second.next; + while ((cur = next)) { + flecs_id_record_assert_empty(cur); + next = cur->second.next; + flecs_id_record_release(world, cur); + } + } else { + /* Iterate (Relationship, *) list */ + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *cur, *next = idr->first.next; + while ((cur = next)) { + flecs_id_record_assert_empty(cur); + next = cur->first.next; + flecs_id_record_release(world, cur); + } + } + + ecs_log_pop_2(); + } + } + + /* Update counters */ + world->info.id_delete_total ++; + world->info.pair_id_count -= ECS_IS_PAIR(id); + world->info.component_id_count -= idr->type_info != NULL; + world->info.tag_id_count -= idr->type_info == NULL; + + /* Unregister the id record from the world & free resources */ + ecs_table_cache_fini(&idr->cache); + flecs_name_index_free(idr->name_index); + ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t); + + ecs_id_t hash = flecs_id_record_hash(id); + if (hash >= FLECS_HI_ID_RECORD_ID) { + ecs_map_remove(&world->id_index_hi, hash); + flecs_bfree(&world->allocators.id_record, idr); + } else { + idr->id = 0; /* Tombstone */ + } + + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); + ecs_os_free(id_str); + } +} + +ecs_id_record_t* flecs_id_record_ensure( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + idr = flecs_id_record_new(world, id); + } + return idr; +} + +ecs_id_record_t* flecs_id_record_get( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + if (id == ecs_pair(EcsIsA, EcsWildcard)) { + return world->idr_isa_wildcard; + } else if (id == ecs_pair(EcsChildOf, EcsWildcard)) { + return world->idr_childof_wildcard; + } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + return world->idr_identifier_name; + } + + ecs_id_t hash = flecs_id_record_hash(id); + ecs_id_record_t *idr = NULL; + if (hash >= FLECS_HI_ID_RECORD_ID) { + idr = ecs_map_get_deref(&world->id_index_hi, ecs_id_record_t, hash); + } else { + idr = &world->id_index_lo[hash]; + if (!idr->id) { + idr = NULL; + } + } + + return idr; +} + +ecs_id_record_t* flecs_query_id_record_get( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + if (ECS_IS_PAIR(id) && (first != EcsWildcard)) { + idr = flecs_id_record_get(world, ecs_pair(EcsUnion, first)); + } + return idr; + } + if (ECS_IS_PAIR(id) && + ECS_PAIR_SECOND(id) == EcsWildcard && + (idr->flags & EcsIdUnion)) + { + idr = flecs_id_record_get(world, + ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + } + + return idr; +} + +void flecs_id_record_claim( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + (void)world; + idr->refcount ++; +} + +int32_t flecs_id_record_release( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + int32_t rc = -- idr->refcount; + ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); + + if (!rc) { + flecs_id_record_free(world, idr); + } + + return rc; +} + +void flecs_id_record_release_tables( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + /* Cache should not contain tables that aren't empty */ + ecs_assert(flecs_table_cache_count(&idr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_iter_t it; + if (flecs_table_cache_empty_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + /* Release current table */ + flecs_table_free(world, tr->hdr.table); + } + } +} + +bool flecs_id_record_set_type_info( + ecs_world_t *world, + ecs_id_record_t *idr, + const ecs_type_info_t *ti) +{ + bool is_wildcard = ecs_id_is_wildcard(idr->id); + if (!is_wildcard) { + if (ti) { + if (!idr->type_info) { + world->info.tag_id_count --; + world->info.component_id_count ++; + } + } else { + if (idr->type_info) { + world->info.tag_id_count ++; + world->info.component_id_count --; + } + } + } + + bool changed = idr->type_info != ti; + idr->type_info = ti; + + return changed; +} + +ecs_hashmap_t* flecs_id_record_name_index_ensure( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + ecs_hashmap_t *map = idr->name_index; + if (!map) { + map = idr->name_index = flecs_name_index_new(world, &world->allocator); + } + + return map; +} + +ecs_hashmap_t* flecs_id_name_index_ensure( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + + return flecs_id_record_name_index_ensure(world, idr); +} + +ecs_hashmap_t* flecs_id_name_index_get( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; + } + + return idr->name_index; +} + +ecs_table_record_t* flecs_table_record_get( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + + ecs_id_record_t* idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; + } + + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); +} + +ecs_table_record_t* flecs_id_record_get_table( + const ecs_id_record_t *idr, + const ecs_table_t *table) +{ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); +} + +void flecs_init_id_records( + ecs_world_t *world) +{ + /* Cache often used id records on world */ + world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); + world->idr_wildcard_wildcard = flecs_id_record_ensure(world, + ecs_pair(EcsWildcard, EcsWildcard)); + world->idr_any = flecs_id_record_ensure(world, EcsAny); + world->idr_isa_wildcard = flecs_id_record_ensure(world, + ecs_pair(EcsIsA, EcsWildcard)); +} + +void flecs_fini_id_records( + ecs_world_t *world) +{ + /* Loop & delete first element until there are no elements left. Id records + * can recursively delete each other, this ensures we always have a + * valid iterator. */ + while (ecs_map_count(&world->id_index_hi) > 0) { + ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi); + ecs_map_next(&it); + flecs_id_record_release(world, ecs_map_ptr(&it)); + } + + int32_t i; + for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { + ecs_id_record_t *idr = &world->id_index_lo[i]; + if (idr->id) { + flecs_id_record_release(world, idr); + } + } + + ecs_assert(ecs_map_count(&world->id_index_hi) == 0, + ECS_INTERNAL_ERROR, NULL); + + ecs_map_fini(&world->id_index_hi); + ecs_os_free(world->id_index_lo); +} + +/** + * @file table.c + * @brief Table storage implementation. + * + * Tables are the data structure that store the component data. Tables have + * columns for each component in the table, and rows for each entity stored in + * the table. Once created, the component list for a table doesn't change, but + * entities can move from one table to another. + * + * Each table has a type, which is a vector with the (component) ids in the + * table. The vector is sorted by id, which ensures that there can be only one + * table for each unique combination of components. + * + * Not all ids in a table have to be components. Tags are ids that have no + * data type associated with them, and as a result don't need to be explicitly + * stored beyond an element in the table type. To save space and speed up table + * creation, each table has a reference to a "storage table", which is a table + * that only includes component ids (so excluding tags). + * + * Note that the actual data is not stored on the storage table. The storage + * table is only used for sharing administration. A column_map member maps + * between column indices of the table and its storage table. Tables are + * refcounted, which ensures that storage tables won't be deleted if other + * tables have references to it. + */ + + +/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as + * this can severely slow down many ECS operations. */ +#ifdef FLECS_SANITIZE +static +void flecs_table_check_sanity(ecs_table_t *table) { + int32_t size = ecs_vec_size(&table->data.entities); + int32_t count = ecs_vec_count(&table->data.entities); + + int32_t i; + int32_t sw_offset = table->_ ? table->_->sw_offset : 0; + int32_t sw_count = table->_ ? table->_->sw_count : 0; + int32_t bs_offset = table->_ ? table->_->bs_offset : 0; + int32_t bs_count = table->_ ? table->_->bs_count : 0; + int32_t type_count = table->type.count; + ecs_id_t *ids = table->type.array; + + ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); + + if (table->column_count) { + int32_t column_count = table->column_count; + ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); + + int32_t *column_map = table->column_map; + ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + ecs_vec_t *column = &table->data.columns[i].data; + ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); + int32_t column_map_id = column_map[i + type_count]; + ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); + } + } else { + ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (sw_count) { + ecs_assert(table->_->sw_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < sw_count; i ++) { + ecs_switch_t *sw = &table->_->sw_columns[i]; + ecs_assert(ecs_vec_count(&sw->values) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, + ECS_INTERNAL_ERROR, NULL); + } + } + + if (bs_count) { + ecs_assert(table->_->bs_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &table->_->bs_columns[i]; + ecs_assert(flecs_bitset_count(bs) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), + ECS_INTERNAL_ERROR, NULL); + } + } + + ecs_assert((table->_->traversable_count == 0) || + (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); +} +#else +#define flecs_table_check_sanity(table) +#endif + +/* Set flags for type hooks so table operations can quickly check whether a + * fast or complex operation that invokes hooks is required. */ +static +ecs_flags32_t flecs_type_info_flags( + const ecs_type_info_t *ti) +{ + ecs_flags32_t flags = 0; + + if (ti->hooks.ctor) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.on_add) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.dtor) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.on_remove) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.copy) { + flags |= EcsTableHasCopy; + } + if (ti->hooks.move) { + flags |= EcsTableHasMove; + } + + return flags; +} + +static +void flecs_table_init_columns( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_count) +{ + if (!column_count) { + return; + } + + int32_t i, cur = 0, ids_count = table->type.count; + ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); + table->data.columns = columns; + + ecs_id_t *ids = table->type.array; + ecs_table_record_t *records = table->_->records; + int32_t *t2s = table->column_map; + int32_t *s2t = &table->column_map[ids_count]; + + for (i = 0; i < ids_count; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + const ecs_type_info_t *ti = idr->type_info; + if (!ti) { + t2s[i] = -1; + continue; + } + + t2s[i] = cur; + s2t[cur] = i; + tr->column = flecs_ito(int16_t, cur); + + columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); + columns[cur].id = ids[i]; + columns[cur].size = ti->size; + + if (ECS_IS_PAIR(ids[i])) { + ecs_table_record_t *wc_tr = flecs_id_record_get_table( + idr->parent, table); + if (wc_tr->index == tr->index) { + wc_tr->column = tr->column; + } + } + +#ifdef FLECS_DEBUG + ecs_vec_init(NULL, &columns[cur].data, ti->size, 0); +#endif + + table->flags |= flecs_type_info_flags(ti); + cur ++; + } +} + +/* Initialize table storage */ +void flecs_table_init_data( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_data_t *storage = &table->data; + ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); + + flecs_table_init_columns(world, table, table->column_count); + + ecs_table__t *meta = table->_; + int32_t i, sw_count = meta->sw_count; + int32_t bs_count = meta->bs_count; + + if (sw_count) { + meta->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count); + for (i = 0; i < sw_count; i ++) { + flecs_switch_init(&meta->sw_columns[i], + &world->allocator, 0); + } + } + + if (bs_count) { + meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&meta->bs_columns[i]); + } + } +} + +/* Initialize table flags. Table flags are used in lots of scenarios to quickly + * check the features of a table without having to inspect the table type. Table + * flags are typically used to early-out of potentially expensive operations. */ +static +void flecs_table_init_flags( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; + + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; + } + + if (id == EcsModule) { + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } else if (id == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + } else if (id == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } else { + if (ECS_IS_PAIR(id)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + + table->flags |= EcsTableHasPairs; + + if (r == EcsIsA) { + table->flags |= EcsTableHasIsA; + } else if (r == EcsChildOf) { + table->flags |= EcsTableHasChildOf; + ecs_entity_t obj = ecs_pair_second(world, id); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + + if (obj == EcsFlecs || obj == EcsFlecsCore || + ecs_has_id(world, obj, EcsModule)) + { + /* If table contains entities that are inside one of the + * builtin modules, it contains builtin entities */ + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } + } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + table->flags |= EcsTableHasName; + } else if (r == EcsUnion) { + ecs_table__t *meta = table->_; + table->flags |= EcsTableHasUnion; + + if (!meta->sw_count) { + meta->sw_offset = flecs_ito(int16_t, i); + } + meta->sw_count ++; + } else if (r == ecs_id(EcsTarget)) { + ecs_table__t *meta = table->_; + table->flags |= EcsTableHasTarget; + meta->ft_offset = flecs_ito(int16_t, i); + } else if (r == ecs_id(EcsPoly)) { + table->flags |= EcsTableHasBuiltins; + } + } else { + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_table__t *meta = table->_; + table->flags |= EcsTableHasToggle; + + if (!meta->bs_count) { + meta->bs_offset = flecs_ito(int16_t, i); + } + meta->bs_count ++; + } + if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { + table->flags |= EcsTableHasOverrides; + } + } + } + } +} + +/* Utility function that appends an element to the table record array */ +static +void flecs_table_append_to_records( + ecs_world_t *world, + ecs_table_t *table, + ecs_vec_t *records, + ecs_id_t id, + int32_t column) +{ + /* To avoid a quadratic search, use the O(1) lookup that the index + * already provides. */ + ecs_id_record_t *idr = flecs_id_record_ensure(world, id); + ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( + idr, table); + if (!tr) { + tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); + tr->index = flecs_ito(int16_t, column); + tr->count = 1; + + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); + } else { + tr->count ++; + } + + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); +} + +/* Main table initialization function */ +void flecs_table_init( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *from) +{ + /* Make sure table->flags is initialized */ + flecs_table_init_flags(world, table); + + /* The following code walks the table type to discover which id records the + * table needs to register table records with. + * + * In addition to registering itself with id records for each id in the + * table type, a table also registers itself with wildcard id records. For + * example, if a table contains (Eats, Apples), it will register itself with + * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it + * easier for wildcard queries to find the relevant tables. */ + + int32_t dst_i = 0, dst_count = table->type.count; + int32_t src_i = 0, src_count = 0; + ecs_id_t *dst_ids = table->type.array; + ecs_id_t *src_ids = NULL; + ecs_table_record_t *tr = NULL, *src_tr = NULL; + if (from) { + src_count = from->type.count; + src_ids = from->type.array; + src_tr = from->_->records; + } + + /* We don't know in advance how large the records array will be, so use + * cached vector. This eliminates unnecessary allocations, and/or expensive + * iterations to determine how many records we need. */ + ecs_allocator_t *a = &world->allocator; + ecs_vec_t *records = &world->store.records; + ecs_vec_reset_t(a, records, ecs_table_record_t); + ecs_id_record_t *idr, *childof_idr = NULL; + + int32_t last_id = -1; /* Track last regular (non-pair) id */ + int32_t first_pair = -1; /* Track the first pair in the table */ + int32_t first_role = -1; /* Track first id with role */ + + /* Scan to find boundaries of regular ids, pairs and roles */ + for (dst_i = 0; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { + first_pair = dst_i; + } + if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { + last_id = dst_i; + } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { + first_role = dst_i; + } + } + + /* The easy part: initialize a record for every id in the type */ + for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_id_t src_id = src_ids[src_i]; + + idr = NULL; + + if (dst_id == src_id) { + ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); + idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; + } else if (dst_id < src_id) { + idr = flecs_id_record_ensure(world, dst_id); + } + if (idr) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)idr; + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 1; + } + + dst_i += dst_id <= src_id; + src_i += dst_id >= src_id; + } + + /* Add remaining ids that the "from" table didn't have */ + for (; (dst_i < dst_count); dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + idr = flecs_id_record_ensure(world, dst_id); + tr->hdr.cache = (ecs_table_cache_t*)idr; + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 1; + } + + /* We're going to insert records from the vector into the index that + * will get patched up later. To ensure the record pointers don't get + * invalidated we need to grow the vector so that it won't realloc as + * we're adding the next set of records */ + if (first_role != -1 || first_pair != -1) { + int32_t start = first_role; + if (first_pair != -1 && (start != -1 || first_pair < start)) { + start = first_pair; + } + + /* Total number of records can never be higher than + * - number of regular (non-pair) ids + + * - three records for pairs: (R,T), (R,*), (*,T) + * - one wildcard (*), one any (_) and one pair wildcard (*,*) record + * - one record for (ChildOf, 0) + */ + int32_t flag_id_count = dst_count - start; + int32_t record_count = start + 3 * flag_id_count + 3 + 1; + ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); + } + + /* Add records for ids with roles (used by cleanup logic) */ + if (first_role != -1) { + for (dst_i = first_role; dst_i < dst_count; dst_i ++) { + ecs_id_t id = dst_ids[dst_i]; + if (!ECS_IS_PAIR(id)) { + ecs_entity_t first = 0; + ecs_entity_t second = 0; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + first = ECS_PAIR_FIRST(id); + second = ECS_PAIR_SECOND(id); + } else { + first = id & ECS_COMPONENT_MASK; + } + if (first) { + flecs_table_append_to_records(world, table, records, + ecs_pair(EcsFlag, first), dst_i); + } + if (second) { + flecs_table_append_to_records(world, table, records, + ecs_pair(EcsFlag, second), dst_i); + } + } + } + } + + int32_t last_pair = -1; + bool has_childof = table->flags & EcsTableHasChildOf; + if (first_pair != -1) { + /* Add a (Relationship, *) record for each relationship. */ + ecs_entity_t r = 0; + for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + if (!ECS_IS_PAIR(dst_id)) { + break; /* no more pairs */ + } + if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ + tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); + + ecs_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache; + r = ECS_PAIR_FIRST(dst_id); + if (r == EcsChildOf) { + childof_idr = p_idr; + } + + idr = p_idr->parent; /* (R, *) */ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)idr; + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 0; + } + + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + tr->count ++; + } + + last_pair = dst_i; + + /* Add a (*, Target) record for each relationship target. Type + * ids are sorted relationship-first, so we can't simply do a single linear + * scan to find all occurrences for a target. */ + for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); + + flecs_table_append_to_records( + world, table, records, tgt_id, dst_i); + } + } + + /* Lastly, add records for all-wildcard ids */ + if (last_id >= 0) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; + tr->index = 0; + tr->count = flecs_ito(int16_t, last_id + 1); + } + if (last_pair - first_pair) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; + tr->index = flecs_ito(int16_t, first_pair); + tr->count = flecs_ito(int16_t, last_pair - first_pair); + } + if (dst_count) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; + tr->index = 0; + tr->count = 1; + } + if (dst_count && !has_childof) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + childof_idr = world->idr_childof_0; + tr->hdr.cache = (ecs_table_cache_t*)childof_idr; + tr->index = 0; + tr->count = 1; + } + + /* Now that all records have been added, copy them to array */ + int32_t i, dst_record_count = ecs_vec_count(records); + ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, + dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); + table->_->record_count = flecs_ito(int16_t, dst_record_count); + table->_->records = dst_tr; + int32_t column_count = 0; + + /* Register & patch up records */ + for (i = 0; i < dst_record_count; i ++) { + tr = &dst_tr[i]; + idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_table_cache_get(&idr->cache, table)) { + /* If this is a target wildcard record it has already been + * registered, but the record is now at a different location in + * memory. Patch up the linked list with the new address */ + ecs_table_cache_replace(&idr->cache, table, &tr->hdr); + } else { + /* Other records are not registered yet */ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); + } + + /* Claim id record so it stays alive as long as the table exists */ + flecs_id_record_claim(world, idr); + + /* Initialize event flags */ + table->flags |= idr->flags & EcsIdEventMask; + + /* Initialize column index (will be overwritten by init_columns) */ + tr->column = -1; + + if (idr->flags & EcsIdAlwaysOverride) { + table->flags |= EcsTableHasOverrides; + } + + if ((i < table->type.count) && (idr->type_info != NULL)) { + column_count ++; + } + } + + if (column_count) { + table->column_map = flecs_walloc_n(world, int32_t, + dst_count + column_count); + } + table->column_count = flecs_ito(int16_t, column_count); + flecs_table_init_data(world, table); + + if (table->flags & EcsTableHasName) { + ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); + table->_->name_index = + flecs_id_record_name_index_ensure(world, childof_idr); + ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (table->flags & EcsTableHasOnTableCreate) { + flecs_emit(world, world, &(ecs_event_desc_t) { + .ids = &table->type, + .event = EcsOnTableCreate, + .table = table, + .flags = EcsEventTableOnly, + .observable = world + }); + } +} + +/* Unregister table from id records */ +static +void flecs_table_records_unregister( + ecs_world_t *world, + ecs_table_t *table) +{ + uint64_t table_id = table->id; + int32_t i, count = table->_->record_count; + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &table->_->records[i]; + ecs_table_cache_t *cache = tr->hdr.cache; + ecs_id_t id = ((ecs_id_record_t*)cache)->id; + + ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, + ECS_INTERNAL_ERROR, NULL); + (void)id; + + ecs_table_cache_remove(cache, table_id, &tr->hdr); + flecs_id_record_release(world, (ecs_id_record_t*)cache); + } + + flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); +} + +/* Keep track for what kind of builtin events observers are registered that can + * potentially match the table. This allows code to early out of calling the + * emit function that notifies observers. */ +static +void flecs_table_add_trigger_flags( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event) +{ + (void)world; + + if (event == EcsOnAdd) { + table->flags |= EcsTableHasOnAdd; + } else if (event == EcsOnRemove) { + table->flags |= EcsTableHasOnRemove; + } else if (event == EcsOnSet) { + table->flags |= EcsTableHasOnSet; + } else if (event == EcsUnSet) { + table->flags |= EcsTableHasUnSet; + } else if (event == EcsOnTableFill) { + table->flags |= EcsTableHasOnTableFill; + } else if (event == EcsOnTableEmpty) { + table->flags |= EcsTableHasOnTableEmpty; + } +} + +/* Invoke OnRemove observers for all entities in table. Useful during table + * deletion or when clearing entities from a table. */ +static +void flecs_table_notify_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + int32_t count = data->entities.count; + if (count) { + flecs_notify_on_remove(world, table, NULL, 0, count, &table->type); + } +} + +/* Invoke type hook for entities in table */ +static +void flecs_table_invoke_hook( + ecs_world_t *world, + ecs_table_t *table, + ecs_iter_action_t callback, + ecs_entity_t event, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count) +{ + void *ptr = ecs_vec_get(&column->data, column->size, row); + flecs_invoke_hook(world, table, count, row, entities, ptr, column->id, + column->ti, event, callback); +} + +/* Construct components */ +static +void flecs_table_invoke_ctor( + ecs_column_t *column, + int32_t row, + int32_t count) +{ + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + void *ptr = ecs_vec_get(&column->data, column->size, row); + ctor(ptr, count, ti); + } +} + +/* Destruct components */ +static +void flecs_table_invoke_dtor( + ecs_column_t *column, + int32_t row, + int32_t count) +{ + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + void *ptr = ecs_vec_get(&column->data, column->size, row); + dtor(ptr, count, ti); + } +} + +/* Run hooks that get invoked when component is added to entity */ +static +void flecs_table_invoke_add_hooks( + ecs_world_t *world, + ecs_table_t *table, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count, + bool construct) +{ + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + if (construct) { + flecs_table_invoke_ctor(column, row, count); + } + + ecs_iter_action_t on_add = ti->hooks.on_add; + if (on_add) { + flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, + entities, row, count); + } +} + +/* Run hooks that get invoked when component is removed from entity */ +static +void flecs_table_invoke_remove_hooks( + ecs_world_t *world, + ecs_table_t *table, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count, + bool dtor) +{ + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (on_remove) { + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, + entities, row, count); + } + + if (dtor) { + flecs_table_invoke_dtor(column, row, count); + } +} + +/* Destruct all components and/or delete all entities in table in range */ +static +void flecs_table_dtor_all( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + bool update_entity_index, + bool is_delete) +{ + /* Can't delete and not update the entity index */ + ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); + + int32_t ids_count = table->column_count; + ecs_entity_t *entities = data->entities.array; + int32_t i, c, end = row + count; + + if (is_delete && table->_->traversable_count) { + /* If table contains monitored entities with traversable relationships, + * make sure to invalidate observer cache */ + flecs_emit_propagate_invalidate(world, table, row, count); + } + + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Throw up a lock just to be sure */ + table->_->lock = true; + + /* Run on_remove callbacks first before destructing components */ + for (c = 0; c < ids_count; c++) { + ecs_column_t *column = &data->columns[c]; + ecs_iter_action_t on_remove = column->ti->hooks.on_remove; + if (on_remove) { + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, + column, &entities[row], row, count); + } + } + + /* Destruct components */ + for (c = 0; c < ids_count; c++) { + flecs_table_invoke_dtor(&data->columns[c], row, count); + } + + /* Iterate entities first, then components. This ensures that only one + * entity is invalidated at a time, which ensures that destructors can + * safely access other entities. */ + for (i = row; i < end; i ++) { + /* Update entity index after invoking destructors so that entity can + * be safely used in destructor callbacks. */ + if (update_entity_index) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), + ECS_INTERNAL_ERROR, NULL); + + if (is_delete) { + flecs_entities_remove(world, e); + ecs_assert(ecs_is_valid(world, e) == false, + ECS_INTERNAL_ERROR, NULL); + } else { + // If this is not a delete, clear the entity index record + ecs_record_t *record = flecs_entities_get(world, e); + record->table = NULL; + record->row = 0; + } + } else { + /* This should only happen in rare cases, such as when the data + * cleaned up is not part of the world (like with snapshots) */ + } + } + + table->_->lock = false; + + /* If table does not have destructors, just update entity index */ + } else if (update_entity_index) { + if (is_delete) { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + flecs_entities_remove(world, e); + ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + } + } else { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_record_t *record = flecs_entities_get(world, e); + record->table = NULL; + record->row = record->row & ECS_ROW_FLAGS_MASK; + (void)e; + } + } + } +} + +/* Cleanup table storage */ +static +void flecs_table_fini_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + bool do_on_remove, + bool update_entity_index, + bool is_delete, + bool deactivate) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + + if (!data) { + return; + } + + if (do_on_remove) { + flecs_table_notify_on_remove(world, table, data); + } + + int32_t count = flecs_table_data_count(data); + if (count) { + flecs_table_dtor_all(world, table, data, 0, count, + update_entity_index, is_delete); + } + + ecs_column_t *columns = data->columns; + if (columns) { + int32_t c, column_count = table->column_count; + for (c = 0; c < column_count; c ++) { + /* Sanity check */ + ecs_assert(columns[c].data.count == data->entities.count, + ECS_INTERNAL_ERROR, NULL); + ecs_vec_fini(&world->allocator, + &columns[c].data, columns[c].size); + } + flecs_wfree_n(world, ecs_column_t, column_count, columns); + data->columns = NULL; + } + + ecs_table__t *meta = table->_; + ecs_switch_t *sw_columns = meta->sw_columns; + if (sw_columns) { + int32_t c, column_count = meta->sw_count; + for (c = 0; c < column_count; c ++) { + flecs_switch_fini(&sw_columns[c]); + } + flecs_wfree_n(world, ecs_switch_t, column_count, sw_columns); + meta->sw_columns = NULL; + } + + ecs_bitset_t *bs_columns = meta->bs_columns; + if (bs_columns) { + int32_t c, column_count = meta->bs_count; + for (c = 0; c < column_count; c ++) { + flecs_bitset_fini(&bs_columns[c]); + } + flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); + meta->bs_columns = NULL; + } + + ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); + + if (deactivate && count) { + flecs_table_set_empty(world, table); + } + + table->_->traversable_count = 0; + table->flags &= ~EcsTableHasTraversable; +} + +ecs_vec_t* flecs_table_entities( + ecs_table_t *table) +{ + return &table->data.entities; +} + +ecs_entity_t* flecs_table_entities_array( + ecs_table_t *table) +{ + return ecs_vec_first(flecs_table_entities(table)); +} + +/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ +void flecs_table_clear_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + flecs_table_fini_data(world, table, data, false, false, false, false); +} + +/* Cleanup, no OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities_silent( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_fini_data(world, table, &table->data, false, true, false, true); +} + +/* Cleanup, run OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_fini_data(world, table, &table->data, true, true, false, true); +} + +/* Cleanup, run OnRemove, delete from entity index, deactivate table */ +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_fini_data(world, table, &table->data, true, true, true, true); +} + +/* Unset all components in table. This function is called before a table is + * deleted, and invokes all UnSet handlers, if any */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + flecs_table_notify_on_remove(world, table, &table->data); +} + +/* Free table resources. */ +void flecs_table_free( + ecs_world_t *world, + ecs_table_t *table) +{ + bool is_root = table == &world->store.root; + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), + ECS_INTERNAL_ERROR, NULL); + (void)world; + + if (!is_root && !(world->flags & EcsWorldQuit)) { + if (table->flags & EcsTableHasOnTableDelete) { + flecs_emit(world, world, &(ecs_event_desc_t) { + .ids = &table->type, + .event = EcsOnTableDelete, + .table = table, + .flags = EcsEventTableOnly, + .observable = world + }); + } + } + + if (ecs_should_log_2()) { + char *expr = ecs_type_str(world, &table->type); + ecs_dbg_2( + "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", + expr, table->id); + ecs_os_free(expr); + ecs_log_push_2(); + } + + world->info.empty_table_count -= (ecs_table_count(table) == 0); + + /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ + flecs_table_fini_data(world, table, &table->data, false, true, true, false); + flecs_table_clear_edges(world, table); + + if (!is_root) { + ecs_type_t ids = { + .array = table->type.array, + .count = table->type.count + }; + + flecs_hashmap_remove_w_hash( + &world->store.table_map, &ids, ecs_table_t*, table->_->hash); + } + + flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); + flecs_wfree_n(world, int32_t, table->column_count + table->type.count, + table->column_map); + flecs_table_records_unregister(world, table); + + /* Update counters */ + world->info.table_count --; + world->info.table_delete_total ++; + + flecs_free_t(&world->allocator, ecs_table__t, table->_); + + if (!(world->flags & EcsWorldFini)) { + ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); + flecs_table_free_type(world, table); + flecs_sparse_remove_t(&world->store.tables, ecs_table_t, table->id); + } + + ecs_log_pop_2(); +} + +/* Free table type. Do this separately from freeing the table as types can be + * in use by application destructors. */ +void flecs_table_free_type( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); +} + +/* Reset a table to its initial state. */ +void flecs_table_reset( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + flecs_table_clear_edges(world, table); +} + +/* Keep track of number of traversable entities in table. A traversable entity + * is an entity used as target in a pair with a traversable relationship. The + * traversable count and flag are used by code to early out of mechanisms like + * event propagation and recursive cleanup. */ +void flecs_table_traversable_add( + ecs_table_t *table, + int32_t value) +{ + int32_t result = table->_->traversable_count += value; + ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); + if (result == 0) { + table->flags &= ~EcsTableHasTraversable; + } else if (result == value) { + table->flags |= EcsTableHasTraversable; + } +} + +/* Mark table column dirty. This usually happens as the result of a set + * operation, or iteration of a query with [out] fields. */ +static +void flecs_table_mark_table_dirty( + ecs_world_t *world, + ecs_table_t *table, + int32_t index) +{ + (void)world; + if (table->dirty_state) { + table->dirty_state[index] ++; + } +} + +/* Mark table component dirty */ +void flecs_table_mark_dirty( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (table->dirty_state) { + ecs_id_record_t *idr = flecs_id_record_get(world, component); + if (!idr) { + return; + } + + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr || tr->column == -1) { + return; + } + + table->dirty_state[tr->column + 1] ++; + } +} + +/* Get (or create) dirty state of table. Used by queries for change tracking */ +int32_t* flecs_table_get_dirty_state( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->dirty_state) { + int32_t column_count = table->column_count; + table->dirty_state = flecs_alloc_n(&world->allocator, + int32_t, column_count + 1); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + for (int i = 0; i < column_count + 1; i ++) { + table->dirty_state[i] = 1; + } + } + return table->dirty_state; +} + +/* Table move logic for switch (union relationship) column */ +static +void flecs_table_move_switch_columns( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + int32_t count, + bool clear) +{ + ecs_table__t *dst_meta = dst_table->_; + ecs_table__t *src_meta = src_table->_; + if (!dst_meta && !src_meta) { + return; + } + + int32_t i_old = 0, src_column_count = src_meta ? src_meta->sw_count : 0; + int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->sw_count : 0; + if (!src_column_count && !dst_column_count) { + return; + } + + ecs_switch_t *src_columns = src_meta ? src_meta->sw_columns : NULL; + ecs_switch_t *dst_columns = dst_meta ? dst_meta->sw_columns : NULL; + + ecs_type_t dst_type = dst_table->type; + ecs_type_t src_type = src_table->type; + + int32_t offset_new = dst_meta ? dst_meta->sw_offset : 0; + int32_t offset_old = src_meta ? src_meta->sw_offset : 0; + + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; + + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_entity_t dst_id = dst_ids[i_new + offset_new]; + ecs_entity_t src_id = src_ids[i_old + offset_old]; + + if (dst_id == src_id) { + ecs_switch_t *src_switch = &src_columns[i_old]; + ecs_switch_t *dst_switch = &dst_columns[i_new]; + + flecs_switch_ensure(dst_switch, dst_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_switch_get(src_switch, src_index + i); + flecs_switch_set(dst_switch, dst_index + i, value); + } + + if (clear) { + ecs_assert(count == flecs_switch_count(src_switch), + ECS_INTERNAL_ERROR, NULL); + flecs_switch_clear(src_switch); + } + } else if (dst_id > src_id) { + ecs_switch_t *src_switch = &src_columns[i_old]; + flecs_switch_clear(src_switch); + } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } + + /* Clear remaining columns */ + if (clear) { + for (; (i_old < src_column_count); i_old ++) { + ecs_switch_t *src_switch = &src_columns[i_old]; + ecs_assert(count == flecs_switch_count(src_switch), + ECS_INTERNAL_ERROR, NULL); + flecs_switch_clear(src_switch); + } + } +} + +/* Table move logic for bitset (toggle component) column */ +static +void flecs_table_move_bitset_columns( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + int32_t count, + bool clear) +{ + ecs_table__t *dst_meta = dst_table->_; + ecs_table__t *src_meta = src_table->_; + if (!dst_meta && !src_meta) { + return; + } + + int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; + int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; + + if (!src_column_count && !dst_column_count) { + return; + } + + ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; + ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; + + ecs_type_t dst_type = dst_table->type; + ecs_type_t src_type = src_table->type; + + int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; + int32_t offset_old = src_meta ? src_meta->bs_offset : 0; + + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; + + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_id_t dst_id = dst_ids[i_new + offset_new]; + ecs_id_t src_id = src_ids[i_old + offset_old]; + + if (dst_id == src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_bitset_t *dst_bs = &dst_columns[i_new]; + + flecs_bitset_ensure(dst_bs, dst_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_bitset_get(src_bs, src_index + i); + flecs_bitset_set(dst_bs, dst_index + i, value); + } + + if (clear) { + ecs_assert(count == flecs_bitset_count(src_bs), + ECS_INTERNAL_ERROR, NULL); + flecs_bitset_fini(src_bs); + } + } else if (dst_id > src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + flecs_bitset_fini(src_bs); + } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } + + /* Clear remaining columns */ + if (clear) { + for (; (i_old < src_column_count); i_old ++) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_assert(count == flecs_bitset_count(src_bs), + ECS_INTERNAL_ERROR, NULL); + flecs_bitset_fini(src_bs); + } + } +} + +/* Grow table column. When a column needs to be reallocated this function takes + * care of correctly invoking ctor/move/dtor hooks. */ +static +void flecs_table_grow_column( + ecs_world_t *world, + ecs_column_t *column, + int32_t to_add, + int32_t dst_size, + bool construct) +{ + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_info_t *ti = column->ti; + int32_t size = column->size; + int32_t count = column->data.count; + int32_t src_size = column->data.size; + int32_t dst_count = count + to_add; + bool can_realloc = dst_size != src_size; + void *result = NULL; + + ecs_assert(dst_size >= dst_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_ctor; + if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { + ecs_xtor_t ctor = ti->hooks.ctor; + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Create vector */ + ecs_vec_t dst; + ecs_vec_init(&world->allocator, &dst, size, dst_size); + dst.count = dst_count; + + void *src_buffer = column->data.array; + void *dst_buffer = dst.array; + + /* Move (and construct) existing elements to new vector */ + move_ctor(dst_buffer, src_buffer, count, ti); + + if (construct) { + /* Construct new element(s) */ + result = ECS_ELEM(dst_buffer, size, count); + ctor(result, to_add, ti); + } + + /* Free old vector */ + ecs_vec_fini(&world->allocator, &column->data, size); + + column->data = dst; + } else { + /* If array won't realloc or has no move, simply add new elements */ + if (can_realloc) { + ecs_vec_set_size(&world->allocator, &column->data, size, dst_size); + } + + result = ecs_vec_grow(&world->allocator, &column->data, size, to_add); + + ecs_xtor_t ctor; + if (construct && (ctor = ti->hooks.ctor)) { + /* If new elements need to be constructed and component has a + * constructor, construct */ + ctor(result, to_add, ti); + } + } + + ecs_assert(column->data.size == dst_size, ECS_INTERNAL_ERROR, NULL); +} + +/* Grow all data structures in a table */ +static +int32_t flecs_table_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 = flecs_table_data_count(data); + int32_t column_count = table->column_count; + + /* Add entity to column with entity ids */ + ecs_vec_set_size_t(&world->allocator, &data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_vec_last_t(&data->entities, ecs_entity_t) + 1; + data->entities.count += to_add; + if (data->entities.size > size) { + size = data->entities.size; + } + + /* Initialize entity ids and record ptrs */ + int32_t i; + if (ids) { + ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); + } else { + ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); + } + + /* Add elements to each column array */ + ecs_column_t *columns = data->columns; + for (i = 0; i < column_count; i ++) { + flecs_table_grow_column(world, &columns[i], to_add, size, true); + ecs_assert(columns[i].data.size == size, ECS_INTERNAL_ERROR, NULL); + flecs_table_invoke_add_hooks(world, table, &columns[i], e, + cur_count, to_add, false); + } + + ecs_table__t *meta = table->_; + int32_t sw_count = meta->sw_count; + int32_t bs_count = meta->bs_count; + ecs_switch_t *sw_columns = meta->sw_columns; + ecs_bitset_t *bs_columns = meta->bs_columns; + + /* Add elements to each switch column */ + for (i = 0; i < sw_count; i ++) { + ecs_switch_t *sw = &sw_columns[i]; + flecs_switch_addn(sw, to_add); + } + + /* Add elements to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, to_add); + } + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + + if (!(world->flags & EcsWorldReadonly) && !cur_count) { + flecs_table_set_empty(world, table); + } + + /* Return index of first added entity */ + return cur_count; +} + +/* Append operation for tables that don't have any complex logic */ +static +void flecs_table_fast_append( + ecs_world_t *world, + ecs_column_t *columns, + int32_t count) +{ + /* Add elements to each column array */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vec_append(&world->allocator, &column->data, column->size); + } +} + +/* Append entity to table */ +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + bool construct, + bool on_add) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!(table->flags & EcsTableHasTarget), + ECS_INVALID_OPERATION, NULL); + + flecs_table_check_sanity(table); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; + int32_t column_count = table->column_count; + ecs_column_t *columns = table->data.columns; + + /* Grow buffer with entity ids, set new element to new entity */ + ecs_entity_t *e = ecs_vec_append_t(&world->allocator, + &data->entities, ecs_entity_t); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + *e = entity; + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & EcsTableIsComplex)) { + flecs_table_fast_append(world, columns, column_count); + if (!count) { + flecs_table_set_empty(world, table); /* See below */ + } + return count; + } + + ecs_entity_t *entities = data->entities.array; + + /* 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. */ + int32_t size = data->entities.size; + + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + flecs_table_grow_column(world, column, 1, size, construct); + + ecs_iter_action_t on_add_hook; + if (on_add && (on_add_hook = column->ti->hooks.on_add)) { + flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, + &entities[count], count, 1); + } + + ecs_assert(columns[i].data.size == + data->entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns[i].data.count == + data->entities.count, ECS_INTERNAL_ERROR, NULL); + } + + ecs_table__t *meta = table->_; + int32_t sw_count = meta->sw_count; + int32_t bs_count = meta->bs_count; + ecs_switch_t *sw_columns = meta->sw_columns; + ecs_bitset_t *bs_columns = meta->bs_columns; + + /* Add element to each switch column */ + for (i = 0; i < sw_count; i ++) { + ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = &sw_columns[i]; + flecs_switch_add(sw); + } + + /* Add element to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, 1); + } + + /* 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 (!count) { + flecs_table_set_empty(world, table); + } + + flecs_table_check_sanity(table); + + return count; +} + +/* Delete last operation for tables that don't have any complex logic */ +static +void flecs_table_fast_delete_last( + ecs_column_t *columns, + int32_t column_count) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_vec_remove_last(&columns[i].data); + } +} + +/* Delete operation for tables that don't have any complex logic */ +static +void flecs_table_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]; + ecs_vec_remove(&column->data, column->size, index); + } +} + +/* Delete entity from table */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + int32_t index, + bool destruct) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!(table->flags & EcsTableHasTarget), + ECS_INVALID_OPERATION, NULL); + + flecs_table_check_sanity(table); + + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; + + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); + + /* Move last entity id to index */ + ecs_entity_t *entities = data->entities.array; + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[index]; + entities[index] = entity_to_move; + ecs_vec_remove_last(&data->entities); + + /* Update record of moved entity in entity index */ + if (index != count) { + ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); + if (record_to_move) { + uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; + record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); + 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 */ + flecs_table_mark_table_dirty(world, table, 0); + + /* If table is empty, deactivate it */ + if (!count) { + flecs_table_set_empty(world, table); + } + + /* Destruct component data */ + ecs_column_t *columns = data->columns; + int32_t column_count = table->column_count; + int32_t i; + + /* If this is a table without lifecycle callbacks or special columns, take + * fast path that just remove an element from the array(s) */ + if (!(table->flags & EcsTableIsComplex)) { + if (index == count) { + flecs_table_fast_delete_last(columns, column_count); + } else { + flecs_table_fast_delete(columns, column_count, index); + } + + flecs_table_check_sanity(table); + return; + } + + /* Last element, destruct & remove */ + if (index == count) { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & EcsTableHasDtors)) { + for (i = 0; i < column_count; i ++) { + flecs_table_invoke_remove_hooks(world, table, &columns[i], + &entity_to_delete, index, 1, true); + } + } + + flecs_table_fast_delete_last(columns, column_count); + + /* Not last element, move last element to deleted element & destruct */ + } else { + /* If table has component destructors, invoke */ + if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_type_info_t *ti = column->ti; + ecs_size_t size = column->size; + void *dst = ecs_vec_get(&column->data, size, index); + void *src = ecs_vec_last(&column->data, size); + + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (destruct && on_remove) { + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, + column, &entity_to_delete, index, 1); + } + + ecs_move_t move_dtor = ti->hooks.move_dtor; + if (move_dtor) { + move_dtor(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + + ecs_vec_remove_last(&column->data); + } + } else { + flecs_table_fast_delete(columns, column_count, index); + } + } + + /* Remove elements from switch columns */ + ecs_table__t *meta = table->_; + ecs_switch_t *sw_columns = meta->sw_columns; + int32_t sw_count = meta->sw_count; + for (i = 0; i < sw_count; i ++) { + flecs_switch_remove(&sw_columns[i], index); + } + + /* Remove elements from bitset columns */ + ecs_bitset_t *bs_columns = meta->bs_columns; + int32_t bs_count = meta->bs_count; + for (i = 0; i < bs_count; i ++) { + flecs_bitset_remove(&bs_columns[i], index); + } + + flecs_table_check_sanity(table); +} + +/* Move operation for tables that don't have any complex logic */ +static +void flecs_table_fast_move( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index) +{ + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; + + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = dst_column->id; + ecs_id_t src_id = src_column->id; + + if (dst_id == src_id) { + int32_t size = dst_column->size; + void *dst = ecs_vec_get(&dst_column->data, size, dst_index); + void *src = ecs_vec_get(&src_column->data, size, src_index); + ecs_os_memcpy(dst, src, size); + } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } +} + +/* Move entity from src to dst table */ +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + bool construct) +{ + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); + + ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); + + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); + + if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { + flecs_table_fast_move(dst_table, dst_index, src_table, src_index); + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); + return; + } + + flecs_table_move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false); + flecs_table_move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false); + + /* If the source and destination entities are the same, move component + * between tables. If the entities are not the same (like when cloning) use + * a copy. */ + bool same_entity = dst_entity == src_entity; + + /* Call move_dtor for moved away from storage only if the entity is at the + * last index in the source table. If it isn't the last entity, the last + * entity in the table will be moved to the src storage, which will take + * care of cleaning up resources. */ + bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); + + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; + + for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = dst_column->id; + ecs_id_t src_id = src_column->id; + + if (dst_id == src_id) { + int32_t size = dst_column->size; + + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *dst = ecs_vec_get(&dst_column->data, size, dst_index); + void *src = ecs_vec_get(&src_column->data, size, src_index); + ecs_type_info_t *ti = dst_column->ti; + + if (same_entity) { + ecs_move_t move = ti->hooks.move_ctor; + if (use_move_dtor || !move) { + /* Also use move_dtor if component doesn't have a move_ctor + * registered, to ensure that the dtor gets called to + * cleanup resources. */ + move = ti->hooks.ctor_move_dtor; + } + + if (move) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + } else { + ecs_copy_t copy = ti->hooks.copy_ctor; + if (copy) { + copy(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + } + } else { + if (dst_id < src_id) { + flecs_table_invoke_add_hooks(world, dst_table, + dst_column, &dst_entity, dst_index, 1, construct); + } else { + flecs_table_invoke_remove_hooks(world, src_table, + src_column, &src_entity, src_index, 1, use_move_dtor); + } + } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } + + for (; (i_new < dst_column_count); i_new ++) { + flecs_table_invoke_add_hooks(world, dst_table, &dst_columns[i_new], + &dst_entity, dst_index, 1, construct); + } + + for (; (i_old < src_column_count); i_old ++) { + flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], + &src_entity, src_index, 1, use_move_dtor); + } + + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); +} + +/* Append n entities to table */ +int32_t flecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t to_add, + const ecs_entity_t *ids) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + + flecs_table_check_sanity(table); + int32_t cur_count = flecs_table_data_count(data); + int32_t result = flecs_table_grow_data( + world, table, data, to_add, cur_count + to_add, ids); + flecs_table_check_sanity(table); + + return result; +} + +/* Set allocated table size */ +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t size) +{ + ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + + flecs_table_check_sanity(table); + + int32_t cur_count = flecs_table_data_count(data); + + if (cur_count < size) { + flecs_table_grow_data(world, table, data, 0, size, NULL); + flecs_table_check_sanity(table); + } +} + +/* Shrink table storage to fit number of entities */ +bool flecs_table_shrink( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + (void)world; + + flecs_table_check_sanity(table); + + ecs_data_t *data = &table->data; + bool has_payload = data->entities.array != NULL; + ecs_vec_reclaim_t(&world->allocator, &data->entities, ecs_entity_t); + + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &data->columns[i]; + ecs_vec_reclaim(&world->allocator, &column->data, column->size); + } + + return has_payload; +} + +/* Return number of entities in table */ +int32_t flecs_table_data_count( + const ecs_data_t *data) +{ + return data ? data->entities.count : 0; +} + +/* Swap operation for switch (union relationship) columns */ +static +void flecs_table_swap_switch_columns( + ecs_table_t *table, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->_->sw_count; + if (!column_count) { + return; + } + + ecs_switch_t *columns = table->_->sw_columns; + + for (i = 0; i < column_count; i ++) { + ecs_switch_t *sw = &columns[i]; + flecs_switch_swap(sw, row_1, row_2); + } +} + +/* Swap operation for bitset (toggle component) columns */ +static +void flecs_table_swap_bitset_columns( + ecs_table_t *table, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->_->bs_count; + if (!column_count) { + return; + } + + ecs_bitset_t *columns = table->_->bs_columns; + + for (i = 0; i < column_count; i ++) { + ecs_bitset_t *bs = &columns[i]; + flecs_bitset_swap(bs, row_1, row_2); + } +} + +/* Swap two rows in a table. Used for table sorting. */ +void flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + int32_t row_1, + int32_t row_2) +{ + (void)world; + + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); + + flecs_table_check_sanity(table); + + if (row_1 == row_2) { + return; + } + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + + ecs_entity_t *entities = table->data.entities.array; + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; + + ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); + ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); + + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep track of whether entity is watched */ + uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); + uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); + + /* Swap entities & records */ + entities[row_1] = e2; + entities[row_2] = e1; + record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); + record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); + + flecs_table_swap_switch_columns(table, row_1, row_2); + flecs_table_swap_bitset_columns(table, row_1, row_2); + + ecs_column_t *columns = table->data.columns; + if (!columns) { + flecs_table_check_sanity(table); + return; + } + + /* Find the maximum size of column elements + * and allocate a temporary buffer for swapping */ + int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; + for (i = 0; i < column_count; i++) { + temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].size); + } + + void* tmp = ecs_os_alloca(temp_buffer_size); + + /* Swap columns */ + for (i = 0; i < column_count; i ++) { + int32_t size = columns[i].size; + void *ptr = columns[i].data.array; + + void *el_1 = ECS_ELEM(ptr, size, row_1); + void *el_2 = ECS_ELEM(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); + } + + flecs_table_check_sanity(table); +} + +static +void flecs_table_merge_vec( + ecs_world_t *world, + ecs_vec_t *dst, + ecs_vec_t *src, + int32_t size, + int32_t elem_size) +{ + int32_t dst_count = dst->count; + + if (!dst_count) { + ecs_vec_fini(&world->allocator, dst, size); + *dst = *src; + src->array = NULL; + src->count = 0; + src->size = 0; + } else { + int32_t src_count = src->count; + + if (elem_size) { + ecs_vec_set_size(&world->allocator, + dst, size, elem_size); + } + ecs_vec_set_count(&world->allocator, + dst, size, dst_count + src_count); + + void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); + void *src_ptr = src->array; + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + + ecs_vec_fini(&world->allocator, src, size); + } +} + +/* Merge data from one table column into other table column */ +static +void flecs_table_merge_column( + ecs_world_t *world, + ecs_column_t *dst, + ecs_column_t *src, + int32_t column_size) +{ + ecs_size_t size = dst->size; + int32_t dst_count = dst->data.count; + + if (!dst_count) { + ecs_vec_fini(&world->allocator, &dst->data, size); + *dst = *src; + src->data.array = NULL; + src->data.count = 0; + src->data.size = 0; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = src->data.count; + + flecs_table_grow_column(world, dst, src_count, column_size, true); + void *dst_ptr = ECS_ELEM(dst->data.array, size, dst_count); + void *src_ptr = src->data.array; + + /* Move values into column */ + ecs_type_info_t *ti = dst->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_move_t move = ti->hooks.move_dtor; + if (move) { + move(dst_ptr, src_ptr, src_count, ti); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + } + + ecs_vec_fini(&world->allocator, &src->data, size); + } +} + +/* Merge storage of two tables. */ +static +void flecs_table_merge_data( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table, + int32_t src_count, + int32_t dst_count, + ecs_data_t *src_data, + ecs_data_t *dst_data) +{ + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + ecs_column_t *src_columns = src_data->columns; + ecs_column_t *dst_columns = dst_data->columns; + + ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); + + if (!src_count) { + return; + } + + /* Merge entities */ + flecs_table_merge_vec(world, &dst_data->entities, &src_data->entities, + ECS_SIZEOF(ecs_entity_t), 0); + ecs_assert(dst_data->entities.count == src_count + dst_count, + ECS_INTERNAL_ERROR, NULL); + int32_t column_size = dst_data->entities.size; + ecs_allocator_t *a = &world->allocator; + + for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = dst_column->id; + ecs_id_t src_id = src_column->id; + + if (dst_id == src_id) { + flecs_table_merge_column(world, dst_column, src_column, column_size); + flecs_table_mark_table_dirty(world, dst_table, i_new + 1); + i_new ++; + i_old ++; + } else if (dst_id < src_id) { + /* New column, make sure vector is large enough. */ + ecs_size_t size = dst_column->size; + ecs_vec_set_size(a, &dst_column->data, size, column_size); + ecs_vec_set_count(a, &dst_column->data, size, src_count + dst_count); + flecs_table_invoke_ctor(dst_column, dst_count, src_count); + i_new ++; + } else if (dst_id > src_id) { + /* Old column does not occur in new table, destruct */ + flecs_table_invoke_dtor(src_column, 0, src_count); + ecs_vec_fini(a, &src_column->data, src_column->size); + i_old ++; + } + } + + flecs_table_move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true); + flecs_table_move_bitset_columns(dst_table, dst_count, src_table, 0, src_count, true); + + /* Initialize remaining columns */ + for (; i_new < dst_column_count; i_new ++) { + ecs_column_t *column = &dst_columns[i_new]; + int32_t size = column->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_vec_set_size(a, &column->data, size, column_size); + ecs_vec_set_count(a, &column->data, size, src_count + dst_count); + flecs_table_invoke_ctor(column, dst_count, src_count); + } + + /* Destruct remaining columns */ + for (; i_old < src_column_count; i_old ++) { + ecs_column_t *column = &src_columns[i_old]; + flecs_table_invoke_dtor(column, 0, src_count); + ecs_vec_fini(a, &column->data, column->size); + } + + /* Mark entity column as dirty */ + flecs_table_mark_table_dirty(world, dst_table, 0); +} + +/* Merge source table into destination table. This typically happens as result + * of a bulk operation, like when a component is removed from all entities in + * the source table (like for the Remove OnDelete policy). */ +void flecs_table_merge( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table, + ecs_data_t *dst_data, + ecs_data_t *src_data) +{ + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); + + flecs_table_check_sanity(src_table); + flecs_table_check_sanity(dst_table); + + bool move_data = false; + + /* If there is nothing to merge to, just clear the old table */ + if (!dst_table) { + flecs_table_clear_data(world, src_table, src_data); + flecs_table_check_sanity(src_table); + return; + } else { + ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); + } + + /* If there is no data to merge, drop out */ + if (!src_data) { + return; + } + + if (!dst_data) { + dst_data = &dst_table->data; + if (dst_table == src_table) { + move_data = true; + } + } + + ecs_entity_t *src_entities = src_data->entities.array; + int32_t src_count = src_data->entities.count; + int32_t dst_count = dst_data->entities.count; + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < src_count; i ++) { + ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); + record->table = dst_table; + } + + /* Merge table columns */ + if (move_data) { + *dst_data = *src_data; + } else { + flecs_table_merge_data(world, dst_table, src_table, src_count, dst_count, + src_data, dst_data); + } + + if (src_count) { + if (!dst_count) { + flecs_table_set_empty(world, dst_table); + } + flecs_table_set_empty(world, src_table); + + flecs_table_traversable_add(dst_table, src_table->_->traversable_count); + flecs_table_traversable_add(src_table, -src_table->_->traversable_count); + ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); + } + + flecs_table_check_sanity(src_table); + flecs_table_check_sanity(dst_table); +} + +/* Replace data with other data. Used by snapshots to restore previous state. */ +void flecs_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); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + + flecs_table_check_sanity(table); + + prev_count = table_data->entities.count; + flecs_table_notify_on_remove(world, table, table_data); + flecs_table_clear_data(world, table, table_data); + + if (data) { + table->data = *data; + } else { + flecs_table_init_data(world, table); + } + + int32_t count = ecs_table_count(table); + + if (!prev_count && count) { + flecs_table_set_empty(world, table); + } else if (prev_count && !count) { + flecs_table_set_empty(world, table); + } + + flecs_table_check_sanity(table); +} + +/* Internal mechanism for propagating information to tables */ +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t *event) +{ + if (world->flags & EcsWorldFini) { + return; + } + + switch(event->kind) { + case EcsTableTriggersForId: + flecs_table_add_trigger_flags(world, table, event->event); + break; + case EcsTableNoTriggersForId: + break; + } +} + +/* -- Public API -- */ + +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (table) { + if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->_->lock ++; + } + } +} + +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (table) { + if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->_->lock --; + ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, NULL); + } + } +} + +const ecs_type_t* ecs_table_get_type( + const ecs_table_t *table) +{ + if (table) { + return &table->type; + } else { + return NULL; + } +} + +int32_t ecs_table_get_type_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return -1; + } + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return -1; + } + + return tr->index; +error: + return -1; +} + +int32_t ecs_table_get_column_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return -1; + } + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return -1; + } + + return tr->column; +error: + return -1; +} + +int32_t ecs_table_column_count( + const ecs_table_t *table) +{ + return table->column_count; +} + +int32_t ecs_table_type_to_column_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); + int32_t *column_map = table->column_map; + if (column_map) { + return column_map[index]; + } +error: + return -1; +} + +int32_t ecs_table_column_to_type_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t offset = table->type.count; + return table->column_map[offset + index]; +error: + return -1; +} + +void* ecs_table_get_column( + const ecs_table_t *table, + int32_t index, + int32_t offset) +{ + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + + ecs_column_t *column = &table->data.columns[index]; + void *result = column->data.array; + if (offset) { + result = ECS_ELEM(result, column->size, offset); + } + + return result; +error: + return NULL; +} + +void* ecs_table_get_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t offset) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + int32_t index = ecs_table_get_column_index(world, table, id); + if (index == -1) { + return NULL; + } + + return ecs_table_get_column(table, index, offset); +error: + return NULL; +} + +size_t ecs_table_get_column_size( + const ecs_table_t *table, + int32_t column) +{ + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); + + return flecs_ito(size_t, table->data.columns[column].size); +error: + return 0; +} + +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_table_data_count(&table->data); +} + +bool ecs_table_has_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + return ecs_table_get_type_index(world, table, id) != -1; +} + +int32_t ecs_table_get_depth( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t rel) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + return flecs_relation_depth(world, rel, table); +error: + return -1; +} + +bool ecs_table_has_flags( + ecs_table_t *table, + ecs_flags32_t flags) +{ + return (table->flags & flags) == flags; +} + +int32_t flecs_table_column_to_union_index( + const ecs_table_t *table, + int32_t column) +{ + int32_t sw_count = table->_->sw_count; + if (sw_count) { + int32_t sw_offset = table->_->sw_offset; + if (column >= sw_offset && column < (sw_offset + sw_count)){ + return column - sw_offset; + } + } + return -1; +} + +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2) +{ + flecs_table_swap(world, table, row_1, row_2); +} + +int32_t flecs_table_observed_count( + const ecs_table_t *table) +{ + return table->_->traversable_count; +} + +void* ecs_record_get_column( + const ecs_record_t *r, + int32_t index, + size_t c_size) +{ + (void)c_size; + ecs_table_t *table = r->table; + + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_column_t *column = &table->data.columns[index]; + ecs_size_t size = column->size; + + ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, + ECS_INVALID_PARAMETER, NULL); + + return ecs_vec_get(&column->data, size, ECS_RECORD_TO_ROW(r->row)); +error: + return NULL; +} + +ecs_record_t* ecs_record_find( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get(world, entity); + if (r) { + return r; + } +error: + return NULL; +} + +/** + * @file table_cache.c + * @brief Data structure for fast table iteration/lookups. + * + * A table cache is a data structure that provides constant time operations for + * insertion and removal of tables, and to testing whether a table is registered + * with the cache. A table cache also provides functions to iterate the tables + * in a cache. + * + * The world stores a table cache per (component) id inside the id record + * administration. Cached queries store a table cache with matched tables. + * + * A table cache has separate lists for non-empty tables and empty tables. This + * improves performance as applications don't waste time iterating empty tables. + */ + + +static +void flecs_table_cache_list_remove( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t *next = elem->next; + ecs_table_cache_hdr_t *prev = elem->prev; + + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; + } + + cache->empty_tables.count -= !!elem->empty; + cache->tables.count -= !elem->empty; + + if (cache->empty_tables.first == elem) { + cache->empty_tables.first = next; + } else if (cache->tables.first == elem) { + cache->tables.first = next; + } + if (cache->empty_tables.last == elem) { + cache->empty_tables.last = prev; + } + if (cache->tables.last == elem) { + cache->tables.last = prev; + } +} + +static +void flecs_table_cache_list_insert( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t *last; + if (elem->empty) { + last = cache->empty_tables.last; + cache->empty_tables.last = elem; + if ((++ cache->empty_tables.count) == 1) { + cache->empty_tables.first = elem; + } + } else { + last = cache->tables.last; + cache->tables.last = elem; + if ((++ cache->tables.count) == 1) { + cache->tables.first = elem; + } + } + + elem->next = NULL; + elem->prev = last; + + if (last) { + last->next = elem; + } +} + +void ecs_table_cache_init( + ecs_world_t *world, + ecs_table_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_init_w_params(&cache->index, &world->allocators.ptr); +} + +void ecs_table_cache_fini( + ecs_table_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_fini(&cache->index); +} + +bool ecs_table_cache_is_empty( + const ecs_table_cache_t *cache) +{ + return ecs_map_count(&cache->index) == 0; +} + +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_table_cache_get(cache, table) == NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + bool empty; + if (!table) { + empty = false; + } else { + empty = ecs_table_count(table) == 0; + } + + result->cache = cache; + result->table = ECS_CONST_CAST(ecs_table_t*, table); + result->empty = empty; + + flecs_table_cache_list_insert(cache, result); + + if (table) { + ecs_map_insert_ptr(&cache->index, table->id, result); + } + + ecs_assert(empty || cache->tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!empty || cache->empty_tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); +} + +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t **r = ecs_map_get_ref( + &cache->index, ecs_table_cache_hdr_t, table->id); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *old = *r; + ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; + if (prev) { + ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); + prev->next = elem; + } + if (next) { + ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); + next->prev = elem; + } + + if (cache->empty_tables.first == old) { + cache->empty_tables.first = elem; + } + if (cache->empty_tables.last == old) { + cache->empty_tables.last = elem; + } + if (cache->tables.first == old) { + cache->tables.first = elem; + } + if (cache->tables.last == old) { + cache->tables.last = elem; + } + + *r = elem; + elem->prev = prev; + elem->next = next; +} + +void* ecs_table_cache_get( + const ecs_table_cache_t *cache, + const ecs_table_t *table) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + if (table) { + if (ecs_map_is_init(&cache->index)) { + return ecs_map_get_deref(&cache->index, void**, table->id); + } + return NULL; + } else { + ecs_table_cache_hdr_t *elem = cache->tables.first; + ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); + return elem; + } +} + +void* ecs_table_cache_remove( + ecs_table_cache_t *cache, + uint64_t table_id, + ecs_table_cache_hdr_t *elem) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table_id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); + + flecs_table_cache_list_remove(cache, elem); + ecs_map_remove(&cache->index, table_id); + + return elem; +} + +bool ecs_table_cache_set_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + bool empty) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, + ecs_table_cache_hdr_t, table->id); + if (!elem) { + return false; + } + + if (elem->empty == empty) { + return false; + } + + flecs_table_cache_list_remove(cache, elem); + elem->empty = empty; + flecs_table_cache_list_insert(cache, elem); + + return true; +} + +bool flecs_table_cache_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->next_list = NULL; + out->cur = NULL; + return out->next != NULL; +} + +bool flecs_table_cache_empty_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->empty_tables.first; + out->next_list = NULL; + out->cur = NULL; + return out->next != NULL; +} + +bool flecs_table_cache_all_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->empty_tables.first; + out->next_list = cache->tables.first; + out->cur = NULL; + return out->next != NULL || out->next_list != NULL; +} + +ecs_table_cache_hdr_t* flecs_table_cache_next_( + ecs_table_cache_iter_t *it) +{ + ecs_table_cache_hdr_t *next = it->next; + if (!next) { + next = it->next_list; + it->next_list = NULL; + if (!next) { + return NULL; + } + } + + it->cur = next; + it->next = next->next; + return next; +} + +/** + * @file table_graph.c + * @brief Data structure to speed up table transitions. + * + * The table graph is used to speed up finding tables in add/remove operations. + * For example, if component C is added to an entity in table [A, B], the entity + * must be moved to table [A, B, C]. The graph speeds this process up with an + * edge for component C that connects [A, B] to [A, B, C]. + */ + + +/* Id sequence (type) utilities */ + +static +uint64_t flecs_type_hash(const void *ptr) { + const ecs_type_t *type = ptr; + ecs_id_t *ids = type->array; + int32_t count = type->count; + return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); +} + +static +int flecs_type_compare(const void *ptr_1, const void *ptr_2) { + const ecs_type_t *type_1 = ptr_1; + const ecs_type_t *type_2 = ptr_2; + + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; + + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); + } + + const ecs_id_t *ids_1 = type_1->array; + const ecs_id_t *ids_2 = type_2->array; + int result = 0; + + int32_t i; + for (i = 0; !result && (i < count_1); i ++) { + ecs_id_t id_1 = ids_1[i]; + ecs_id_t id_2 = ids_2[i]; + result = (id_1 > id_2) - (id_1 < id_2); + } + + return result; +} + +void flecs_table_hashmap_init( + ecs_world_t *world, + ecs_hashmap_t *hm) +{ + flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, + flecs_type_hash, flecs_type_compare, &world->allocator); +} + +/* Find location where to insert id into type */ +static +int flecs_type_find_insert( + const ecs_type_t *type, + int32_t offset, + ecs_id_t to_add) +{ + ecs_id_t *array = type->array; + int32_t i, count = type->count; + + for (i = offset; i < count; i ++) { + ecs_id_t id = array[i]; + if (id == to_add) { + return -1; + } + if (id > to_add) { + return i; + } + } + return i; +} + +/* Find location of id in type */ +static +int flecs_type_find( + const ecs_type_t *type, + ecs_id_t id) +{ + ecs_id_t *array = type->array; + int32_t i, count = type->count; + + for (i = 0; i < count; i ++) { + ecs_id_t cur = array[i]; + if (ecs_id_match(cur, id)) { + return i; + } + if (cur > id) { + return -1; + } + } + + return -1; +} + +/* Count number of matching ids */ +static +int flecs_type_count_matches( + const ecs_type_t *type, + ecs_id_t wildcard, + int32_t offset) +{ + ecs_id_t *array = type->array; + int32_t i = offset, count = type->count; + + for (; i < count; i ++) { + ecs_id_t cur = array[i]; + if (!ecs_id_match(cur, wildcard)) { + break; + } + } + + return i - offset; +} + +/* Create type from source type with id */ +static +int flecs_type_new_with( + ecs_world_t *world, + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t with) +{ + ecs_id_t *src_array = src->array; + int32_t at = flecs_type_find_insert(src, 0, with); + if (at == -1) { + return -1; + } + + int32_t dst_count = src->count + 1; + ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); + dst->count = dst_count; + dst->array = dst_array; + + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t remain = src->count - at; + if (remain) { + ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); + } + + dst_array[at] = with; + + return 0; +} + +/* Create type from source type without ids matching wildcard */ +static +int flecs_type_new_filtered( + ecs_world_t *world, + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t wildcard, + int32_t at) +{ + *dst = flecs_type_copy(world, src); + ecs_id_t *dst_array = dst->array; + ecs_id_t *src_array = src->array; + if (at) { + ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t i = at + 1, w = at, count = src->count; + for (; i < count; i ++) { + ecs_id_t id = src_array[i]; + if (!ecs_id_match(id, wildcard)) { + dst_array[w] = id; + w ++; + } + } + + dst->count = w; + if (w != count) { + dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); + } + + return 0; +} + +/* Create type from source type without id */ +static +int flecs_type_new_without( + ecs_world_t *world, + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t without) +{ + ecs_id_t *src_array = src->array; + int32_t count = 1, at = flecs_type_find(src, without); + if (at == -1) { + return -1; + } + + int32_t src_count = src->count; + if (src_count == 1) { + dst->array = NULL; + dst->count = 0; + return 0; + } + + if (ecs_id_is_wildcard(without)) { + if (ECS_IS_PAIR(without)) { + ecs_entity_t r = ECS_PAIR_FIRST(without); + ecs_entity_t o = ECS_PAIR_SECOND(without); + if (r == EcsWildcard && o != EcsWildcard) { + return flecs_type_new_filtered(world, dst, src, without, at); + } + } + count += flecs_type_count_matches(src, without, at + 1); + } + + int32_t dst_count = src_count - count; + dst->count = dst_count; + if (!dst_count) { + dst->array = NULL; + return 0; + } + + ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); + dst->array = dst_array; + + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t remain = dst_count - at; + if (remain) { + ecs_os_memcpy_n( + &dst_array[at], &src_array[at + count], ecs_id_t, remain); + } + + return 0; +} + +/* Copy type */ +ecs_type_t flecs_type_copy( + ecs_world_t *world, + const ecs_type_t *src) +{ + int32_t src_count = src->count; + if (!src_count) { + return (ecs_type_t){ 0 }; + } + + ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count); + ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count); + return (ecs_type_t) { + .array = ids, + .count = src_count + }; +} + +/* Free type */ +void flecs_type_free( + ecs_world_t *world, + ecs_type_t *type) +{ + int32_t count = type->count; + if (count) { + flecs_wfree_n(world, ecs_id_t, type->count, type->array); + } +} + +/* Add to type */ +static +void flecs_type_add( + ecs_world_t *world, + ecs_type_t *type, + ecs_id_t add) +{ + ecs_type_t new_type; + int res = flecs_type_new_with(world, &new_type, type, add); + if (res != -1) { + flecs_type_free(world, type); + type->array = new_type.array; + type->count = new_type.count; + } +} + +/* Graph edge utilities */ + +void flecs_table_diff_builder_init( + ecs_world_t *world, + ecs_table_diff_builder_t *builder) +{ + ecs_allocator_t *a = &world->allocator; + ecs_vec_init_t(a, &builder->added, ecs_id_t, 256); + ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256); +} + +void flecs_table_diff_builder_fini( + ecs_world_t *world, + ecs_table_diff_builder_t *builder) +{ + ecs_allocator_t *a = &world->allocator; + ecs_vec_fini_t(a, &builder->added, ecs_id_t); + ecs_vec_fini_t(a, &builder->removed, ecs_id_t); +} + +void flecs_table_diff_builder_clear( + ecs_table_diff_builder_t *builder) +{ + ecs_vec_clear(&builder->added); + ecs_vec_clear(&builder->removed); +} + +static +void flecs_table_diff_build_type( + ecs_world_t *world, + ecs_vec_t *vec, + ecs_type_t *type, + int32_t offset) +{ + int32_t count = vec->count - offset; + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + if (count) { + type->array = flecs_wdup_n(world, ecs_id_t, count, + ECS_ELEM_T(vec->array, ecs_id_t, offset)); + type->count = count; + ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset); + } +} + +void flecs_table_diff_build( + ecs_world_t *world, + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff, + int32_t added_offset, + int32_t removed_offset) +{ + flecs_table_diff_build_type(world, &builder->added, &diff->added, + added_offset); + flecs_table_diff_build_type(world, &builder->removed, &diff->removed, + removed_offset); +} + +void flecs_table_diff_build_noalloc( + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff) +{ + diff->added = (ecs_type_t){ + .array = builder->added.array, .count = builder->added.count }; + diff->removed = (ecs_type_t){ + .array = builder->removed.array, .count = builder->removed.count }; +} + +static +void flecs_table_diff_build_add_type_to_vec( + ecs_world_t *world, + ecs_vec_t *vec, + ecs_type_t *add) +{ + if (!add || !add->count) { + return; + } + + int32_t offset = vec->count; + ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); + ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), + add->array, ecs_id_t, add->count); +} + +void flecs_table_diff_build_append_table( + ecs_world_t *world, + ecs_table_diff_builder_t *dst, + ecs_table_diff_t *src) +{ + flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); + flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); +} + +static +void flecs_table_diff_free( + ecs_world_t *world, + ecs_table_diff_t *diff) +{ + flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); + flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); + flecs_bfree(&world->allocators.table_diff, diff); +} + +static +ecs_graph_edge_t* flecs_table_ensure_hi_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id) +{ + if (!edges->hi) { + edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); + ecs_map_init_w_params(edges->hi, &world->allocators.ptr); + } + + ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); + ecs_graph_edge_t *edge = r[0]; + if (edge) { + return edge; + } + + if (id < FLECS_HI_COMPONENT_ID) { + edge = &edges->lo[id]; + } else { + edge = flecs_bcalloc(&world->allocators.graph_edge); + } + + r[0] = edge; + return edge; +} + +static +ecs_graph_edge_t* flecs_table_ensure_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id) +{ + ecs_graph_edge_t *edge; + + if (id < FLECS_HI_COMPONENT_ID) { + if (!edges->lo) { + edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); + } + edge = &edges->lo[id]; + } else { + edge = flecs_table_ensure_hi_edge(world, edges, id); + } + + return edge; +} + +static +void flecs_table_disconnect_edge( + ecs_world_t *world, + ecs_id_t id, + ecs_graph_edge_t *edge) +{ + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); + (void)id; + + /* Remove backref from destination table */ + ecs_graph_edge_hdr_t *next = edge->hdr.next; + ecs_graph_edge_hdr_t *prev = edge->hdr.prev; + + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; + } + + /* Remove data associated with edge */ + ecs_table_diff_t *diff = edge->diff; + if (diff) { + flecs_table_diff_free(world, diff); + } + + /* If edge id is low, clear it from fast lookup array */ + if (id < FLECS_HI_COMPONENT_ID) { + ecs_os_memset_t(edge, 0, ecs_graph_edge_t); + } else { + flecs_bfree(&world->allocators.graph_edge, edge); + } +} + +static +void flecs_table_remove_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id, + ecs_graph_edge_t *edge) +{ + ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_table_disconnect_edge(world, id, edge); + ecs_map_remove(edges->hi, id); +} + +static +void flecs_table_init_edges( + ecs_graph_edges_t *edges) +{ + edges->lo = NULL; + edges->hi = NULL; +} + +static +void flecs_table_init_node( + ecs_graph_node_t *node) +{ + flecs_table_init_edges(&node->add); + flecs_table_init_edges(&node->remove); +} + +bool flecs_table_records_update_empty( + ecs_table_t *table) +{ + bool result = false; + bool is_empty = ecs_table_count(table) == 0; + + int32_t i, count = table->_->record_count; + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &table->_->records[i]; + ecs_table_cache_t *cache = tr->hdr.cache; + result |= ecs_table_cache_set_empty(cache, table, is_empty); + } + + return result; +} + +static +void flecs_init_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *prev) +{ + table->flags = 0; + table->dirty_state = NULL; + table->_->lock = 0; + table->_->generation = 0; + + flecs_table_init_node(&table->node); + + flecs_table_init(world, table, prev); +} + +static +ecs_table_t *flecs_create_table( + ecs_world_t *world, + ecs_type_t *type, + flecs_hashmap_result_t table_elem, + ecs_table_t *prev) +{ + ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); + ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); + + result->id = flecs_sparse_last_id(&world->store.tables); + result->type = *type; + + if (ecs_should_log_2()) { + char *expr = ecs_type_str(world, &result->type); + ecs_dbg_2( + "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", + expr, result->id); + ecs_os_free(expr); + } + + ecs_log_push_2(); + + /* Store table in table hashmap */ + *(ecs_table_t**)table_elem.value = result; + + /* Set keyvalue to one that has the same lifecycle as the table */ + *(ecs_type_t*)table_elem.key = result->type; + result->_->hash = table_elem.hash; + + flecs_init_table(world, result, prev); + + /* Update counters */ + world->info.table_count ++; + world->info.empty_table_count ++; + world->info.table_create_total ++; + + ecs_log_pop_2(); + + return result; +} + +static +ecs_table_t* flecs_table_ensure( + ecs_world_t *world, + ecs_type_t *type, + bool own_type, + ecs_table_t *prev) +{ + ecs_poly_assert(world, ecs_world_t); + + int32_t id_count = type->count; + if (!id_count) { + return &world->store.root; + } + + ecs_table_t *table; + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + &world->store.table_map, type, ecs_table_t*); + if ((table = *(ecs_table_t**)elem.value)) { + if (own_type) { + flecs_type_free(world, type); + } + 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->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + + /* If we get here, the table has not been found, so create it. */ + if (own_type) { + return flecs_create_table(world, type, elem, prev); + } + + ecs_type_t copy = flecs_type_copy(world, type); + return flecs_create_table(world, ©, elem, prev); +} + +static +void flecs_diff_insert_added( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, + ecs_id_t id) +{ + ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; +} + +static +void flecs_diff_insert_removed( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, + ecs_id_t id) +{ + ecs_allocator_t *a = &world->allocator; + ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; +} + +static +void flecs_compute_table_diff( + ecs_world_t *world, + ecs_table_t *node, + ecs_table_t *next, + ecs_graph_edge_t *edge, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair( + ECS_PAIR_FIRST(id), EcsWildcard)); + if (idr->flags & EcsIdUnion) { + if (node != next) { + id = ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)); + } else { + ecs_table_diff_t *diff = flecs_bcalloc( + &world->allocators.table_diff); + diff->added.count = 1; + diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); + edge->diff = diff; + return; + } + } + } + + ecs_type_t node_type = node->type; + ecs_type_t next_type = next->type; + + ecs_id_t *ids_node = node_type.array; + ecs_id_t *ids_next = next_type.array; + int32_t i_node = 0, node_count = node_type.count; + int32_t i_next = 0, next_count = next_type.count; + int32_t added_count = 0; + int32_t removed_count = 0; + bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); + + /* First do a scan to see how big the diff is, so we don't have to realloc + * or alloc more memory than required. */ + for (; i_node < node_count && i_next < next_count; ) { + ecs_id_t id_node = ids_node[i_node]; + ecs_id_t id_next = ids_next[i_next]; + + bool added = id_next < id_node; + bool removed = id_node < id_next; + + trivial_edge &= !added || id_next == id; + trivial_edge &= !removed || id_node == id; + + added_count += added; + removed_count += removed; + + i_node += id_node <= id_next; + i_next += id_next <= id_node; + } + + added_count += next_count - i_next; + removed_count += node_count - i_node; + + trivial_edge &= (added_count + removed_count) <= 1 && + !ecs_id_is_wildcard(id); + + if (trivial_edge) { + /* If edge is trivial there's no need to create a diff element for it */ + return; + } + + ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; + int32_t added_offset = builder->added.count; + int32_t removed_offset = builder->removed.count; + + for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { + ecs_id_t id_node = ids_node[i_node]; + ecs_id_t id_next = ids_next[i_next]; + + if (id_next < id_node) { + flecs_diff_insert_added(world, builder, id_next); + } else if (id_node < id_next) { + flecs_diff_insert_removed(world, builder, id_node); + } + + i_node += id_node <= id_next; + i_next += id_next <= id_node; + } + + for (; i_next < next_count; i_next ++) { + flecs_diff_insert_added(world, builder, ids_next[i_next]); + } + for (; i_node < node_count; i_node ++) { + flecs_diff_insert_removed(world, builder, ids_node[i_node]); + } + + ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); + edge->diff = diff; + flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); + + ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); +} + +static +void flecs_add_overrides_for_base( + ecs_world_t *world, + ecs_type_t *dst_type, + ecs_id_t pair) +{ + ecs_entity_t base = ecs_pair_second(world, pair); + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *base_table = ecs_get_table(world, base); + if (!base_table) { + return; + } + + ecs_id_t *ids = base_table->type.array; + + ecs_flags32_t flags = base_table->flags; + if (flags & EcsTableHasOverrides) { + int32_t i, count = base_table->type.count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + ecs_id_t to_add = 0; + if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { + to_add = id & ~ECS_OVERRIDE; + } else { + ecs_table_record_t *tr = &base_table->_->records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + if (idr->flags & EcsIdAlwaysOverride) { + to_add = id; + } + } + if (to_add) { + ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard); + bool exclusive = false; + if (ECS_IS_PAIR(to_add)) { + ecs_id_record_t *idr = flecs_id_record_get(world, wc); + if (idr) { + exclusive = (idr->flags & EcsIdExclusive) != 0; + } + } + if (!exclusive) { + flecs_type_add(world, dst_type, to_add); + } else { + int32_t column = flecs_type_find(dst_type, wc); + if (column == -1) { + flecs_type_add(world, dst_type, to_add); + } else { + dst_type->array[column] = to_add; + } + } + } + } + } + + if (flags & EcsTableHasIsA) { + ecs_table_record_t *tr = flecs_id_record_get_table( + world->idr_isa_wildcard, base_table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = tr->index, end = i + tr->count; + for (; i != end; i ++) { + flecs_add_overrides_for_base(world, dst_type, ids[i]); + } + } +} + +static +void flecs_add_with_property( + ecs_world_t *world, + ecs_id_record_t *idr_with_wildcard, + ecs_type_t *dst_type, + ecs_entity_t r, + ecs_entity_t o) +{ + r = ecs_get_alive(world, r); + + /* Check if component/relationship has With pairs, which contain ids + * that need to be added to the table. */ + ecs_table_t *table = ecs_get_table(world, r); + if (!table) { + return; + } + + ecs_table_record_t *tr = flecs_id_record_get_table( + idr_with_wildcard, table); + if (tr) { + int32_t i = tr->index, end = i + tr->count; + ecs_id_t *ids = table->type.array; + + for (; i < end; i ++) { + ecs_id_t id = ids[i]; + ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); + ecs_id_t ra = ECS_PAIR_SECOND(id); + ecs_id_t a = ra; + if (o) { + a = ecs_pair(ra, o); + } + + flecs_type_add(world, dst_type, a); + flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); + } + } + +} + +static +ecs_table_t* flecs_find_table_with( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t with) +{ + ecs_ensure_id(world, with); + + ecs_id_record_t *idr = NULL; + ecs_entity_t r = 0, o = 0; + + if (ECS_IS_PAIR(with)) { + r = ECS_PAIR_FIRST(with); + o = ECS_PAIR_SECOND(with); + idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard)); + if (idr->flags & EcsIdUnion) { + ecs_type_t dst_type; + ecs_id_t union_id = ecs_pair(EcsUnion, r); + int res = flecs_type_new_with( + world, &dst_type, &node->type, union_id); + if (res == -1) { + return node; + } + + return flecs_table_ensure(world, &dst_type, true, node); + } else if (idr->flags & EcsIdExclusive) { + /* Relationship is exclusive, check if table already has it */ + ecs_table_record_t *tr = flecs_id_record_get_table(idr, node); + if (tr) { + /* Table already has an instance of the relationship, create + * a new id sequence with the existing id replaced */ + ecs_type_t dst_type = flecs_type_copy(world, &node->type); + ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL); + dst_type.array[tr->index] = with; + return flecs_table_ensure(world, &dst_type, true, node); + } + } + } else { + idr = flecs_id_record_ensure(world, with); + r = with; + } + + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_with(world, &dst_type, &node->type, with); + if (res == -1) { + return node; /* Current table already has id */ + } + + if (r == EcsIsA) { + /* If adding a prefab, check if prefab has overrides */ + flecs_add_overrides_for_base(world, &dst_type, with); + } else if (r == EcsChildOf) { + o = ecs_get_alive(world, o); + if (ecs_has_id(world, o, EcsPrefab)) { + flecs_type_add(world, &dst_type, EcsPrefab); + } + } + + if (idr->flags & EcsIdWith) { + ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world, + ecs_pair(EcsWith, EcsWildcard)); + /* If id has With property, add targets to type */ + flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); + } + + return flecs_table_ensure(world, &dst_type, true, node); +} + +static +ecs_table_t* flecs_find_table_without( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t without) +{ + if (ECS_IS_PAIR(without)) { + ecs_entity_t r = 0; + ecs_id_record_t *idr = NULL; + r = ECS_PAIR_FIRST(without); + idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); + if (idr && idr->flags & EcsIdUnion) { + without = ecs_pair(EcsUnion, r); + } + } + + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_without(world, &dst_type, &node->type, without); + if (res == -1) { + return node; /* Current table does not have id */ + } + + return flecs_table_ensure(world, &dst_type, true, node); +} + +static +void flecs_table_init_edge( + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); + + edge->from = table; + edge->to = to; + edge->id = id; +} + +static +void flecs_init_edge_for_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) +{ + flecs_table_init_edge(table, edge, id, to); + + flecs_table_ensure_hi_edge(world, &table->node.add, id); + + if (table != to || table->flags & EcsTableHasUnion) { + /* Add edges are appended to refs.next */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *next = to_refs->next; + + to_refs->next = &edge->hdr; + edge->hdr.prev = to_refs; + + edge->hdr.next = next; + if (next) { + next->prev = &edge->hdr; + } + + flecs_compute_table_diff(world, table, to, edge, id); + } +} + +static +void flecs_init_edge_for_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) +{ + flecs_table_init_edge(table, edge, id, to); + + flecs_table_ensure_hi_edge(world, &table->node.remove, id); + + if (table != to) { + /* Remove edges are appended to refs.prev */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *prev = to_refs->prev; + + to_refs->prev = &edge->hdr; + edge->hdr.next = to_refs; + + edge->hdr.prev = prev; + if (prev) { + prev->next = &edge->hdr; + } + + flecs_compute_table_diff(world, table, to, edge, id); + } +} + +static +ecs_table_t* flecs_create_edge_for_remove( + ecs_world_t *world, + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) +{ + ecs_table_t *to = flecs_find_table_without(world, node, id); + flecs_init_edge_for_remove(world, node, edge, id, to); + return to; +} + +static +ecs_table_t* flecs_create_edge_for_add( + ecs_world_t *world, + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) +{ + ecs_table_t *to = flecs_find_table_with(world, node, id); + flecs_init_edge_for_add(world, node, edge, id, to); + return to; +} + +ecs_table_t* flecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff) +{ + ecs_poly_assert(world, ecs_world_t); + + node = node ? node : &world->store.root; + + /* Removing 0 from an entity is not valid */ + ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_id_t id = id_ptr[0]; + ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.remove, id); + ecs_table_t *to = edge->to; + + if (!to) { + to = flecs_create_edge_for_remove(world, node, edge, id); + ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (node != to) { + if (edge->diff) { + *diff = *edge->diff; + } else { + diff->added.count = 0; + diff->removed.array = id_ptr; + diff->removed.count = 1; + } + } + + return to; +error: + return NULL; +} + +ecs_table_t* flecs_table_traverse_add( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + + node = node ? node : &world->store.root; + + /* Adding 0 to an entity is not valid */ + ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_id_t id = id_ptr[0]; + ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id); + ecs_table_t *to = edge->to; + + if (!to) { + to = flecs_create_edge_for_add(world, node, edge, id); + ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (node != to || edge->diff) { + if (edge->diff) { + *diff = *edge->diff; + } else { + diff->added.array = id_ptr; + diff->added.count = 1; + diff->removed.count = 0; + } + } + + return to; +error: + return NULL; +} + +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + ecs_type_t *type) +{ + ecs_poly_assert(world, ecs_world_t); + return flecs_table_ensure(world, type, false, NULL); +} + +void flecs_init_root_table( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + + world->store.root.type = (ecs_type_t){0}; + world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); + flecs_init_table(world, &world->store.root, NULL); + + /* Ensure table indices start at 1, as 0 is reserved for the root */ + uint64_t new_id = flecs_sparse_new_id(&world->store.tables); + ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); + (void)new_id; +} + +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + ecs_poly_assert(world, ecs_world_t); + + ecs_log_push_1(); + + ecs_map_iter_t it; + ecs_graph_node_t *table_node = &table->node; + ecs_graph_edges_t *node_add = &table_node->add; + ecs_graph_edges_t *node_remove = &table_node->remove; + ecs_map_t *add_hi = node_add->hi; + ecs_map_t *remove_hi = node_remove->hi; + ecs_graph_edge_hdr_t *node_refs = &table_node->refs; + + /* Cleanup outgoing edges */ + it = ecs_map_iter(add_hi); + while (ecs_map_next(&it)) { + flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); + } + + it = ecs_map_iter(remove_hi); + while (ecs_map_next(&it)) { + flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); + } + + /* Cleanup incoming add edges */ + ecs_graph_edge_hdr_t *next, *cur = node_refs->next; + if (cur) { + do { + ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; + ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); + next = cur->next; + flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge); + } while ((cur = next)); + } + + /* Cleanup incoming remove edges */ + cur = node_refs->prev; + if (cur) { + do { + ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; + ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); + next = cur->prev; + flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge); + } while ((cur = next)); + } + + if (node_add->lo) { + flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo); + } + if (node_remove->lo) { + flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo); + } + + ecs_map_fini(add_hi); + ecs_map_fini(remove_hi); + flecs_free_t(&world->allocator, ecs_map_t, add_hi); + flecs_free_t(&world->allocator, ecs_map_t, remove_hi); + table_node->add.lo = NULL; + table_node->remove.lo = NULL; + table_node->add.hi = NULL; + table_node->remove.hi = NULL; + + ecs_log_pop_1(); +} + +/* Public convenience functions for traversing table graph */ +ecs_table_t* ecs_table_add_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_table_diff_t diff; + return flecs_table_traverse_add(world, table, &id, &diff); +} + +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_table_diff_t diff; + return flecs_table_traverse_remove(world, table, &id, &diff); +} + +ecs_table_t* ecs_table_find( + ecs_world_t *world, + const ecs_id_t *ids, + int32_t id_count) +{ + ecs_type_t type = { + .array = ECS_CONST_CAST(ecs_id_t*, ids), + .count = id_count + }; + return flecs_table_ensure(world, &type, false, NULL); +} + +/** + * @file expr/deserialize.c + * @brief Deserialize flecs string format into (component) values. + */ + +#include + +#ifdef FLECS_EXPR + +/* String deserializer for values & simple expressions */ + +/* Order in enumeration is important, as it is used for precedence */ +typedef enum ecs_expr_oper_t { + EcsExprOperUnknown, + EcsLeftParen, + EcsCondAnd, + EcsCondOr, + EcsCondEq, + EcsCondNeq, + EcsCondGt, + EcsCondGtEq, + EcsCondLt, + EcsCondLtEq, + EcsShiftLeft, + EcsShiftRight, + EcsAdd, + EcsSub, + EcsMul, + EcsDiv, + EcsMin +} ecs_expr_oper_t; + +/* Used to track temporary values */ +#define EXPR_MAX_STACK_SIZE (256) + +typedef struct ecs_expr_value_t { + const ecs_type_info_t *ti; + void *ptr; +} ecs_expr_value_t; + +typedef struct ecs_value_stack_t { + ecs_expr_value_t values[EXPR_MAX_STACK_SIZE]; + ecs_stack_cursor_t *cursor; + ecs_stack_t *stack; + ecs_stage_t *stage; + int32_t count; +} ecs_value_stack_t; + +static +const char* flecs_parse_expr( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *ptr, + ecs_value_t *value, + ecs_expr_oper_t op, + const ecs_parse_expr_desc_t *desc); + +static +void* flecs_expr_value_new( + ecs_value_stack_t *stack, + ecs_entity_t type) +{ + ecs_stage_t *stage = stack->stage; + ecs_world_t *world = stage->world; + ecs_id_record_t *idr = flecs_id_record_get(world, type); + if (!idr) { + return NULL; + } + + const ecs_type_info_t *ti = idr->type_info; + if (!ti) { + return NULL; + } + + ecs_assert(ti->size != 0, ECS_INTERNAL_ERROR, NULL); + void *result = flecs_stack_alloc(stack->stack, ti->size, ti->alignment); + if (ti->hooks.ctor) { + ti->hooks.ctor(result, 1, ti); + } else { + ecs_os_memset(result, 0, ti->size); + } + if (ti->hooks.dtor) { + /* Track values that have destructors */ + stack->values[stack->count].ti = ti; + stack->values[stack->count].ptr = result; + stack->count ++; + } + + return result; +} + +static +const char* flecs_str_to_expr_oper( + const char *str, + ecs_expr_oper_t *op) +{ + if (!ecs_os_strncmp(str, "+", 1)) { + *op = EcsAdd; + return str + 1; + } else if (!ecs_os_strncmp(str, "-", 1)) { + *op = EcsSub; + return str + 1; + } else if (!ecs_os_strncmp(str, "*", 1)) { + *op = EcsMul; + return str + 1; + } else if (!ecs_os_strncmp(str, "/", 1)) { + *op = EcsDiv; + return str + 1; + } else if (!ecs_os_strncmp(str, "&&", 2)) { + *op = EcsCondAnd; + return str + 2; + } else if (!ecs_os_strncmp(str, "||", 2)) { + *op = EcsCondOr; + return str + 2; + } else if (!ecs_os_strncmp(str, "==", 2)) { + *op = EcsCondEq; + return str + 2; + } else if (!ecs_os_strncmp(str, "!=", 2)) { + *op = EcsCondNeq; + return str + 2; + } else if (!ecs_os_strncmp(str, ">=", 2)) { + *op = EcsCondGtEq; + return str + 2; + } else if (!ecs_os_strncmp(str, "<=", 2)) { + *op = EcsCondLtEq; + return str + 2; + } else if (!ecs_os_strncmp(str, ">>", 2)) { + *op = EcsShiftRight; + return str + 2; + } else if (!ecs_os_strncmp(str, "<<", 2)) { + *op = EcsShiftLeft; + return str + 2; + } else if (!ecs_os_strncmp(str, ">", 1)) { + *op = EcsCondGt; + return str + 1; + } else if (!ecs_os_strncmp(str, "<", 1)) { + *op = EcsCondLt; + return str + 1; + } + + *op = EcsExprOperUnknown; + return NULL; +} + +const char *ecs_parse_expr_token( + const char *name, + const char *expr, + const char *ptr, + char *token) +{ + char *token_ptr = token; + + if (ptr[0] == '/') { + char ch; + if (ptr[1] == '/') { + // Single line comment + for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} + token[0] = 0; + return ecs_parse_ws_eol(ptr); + } else if (ptr[1] == '*') { + // Multi line comment + for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { + if (ch == '*' && ptr[1] == '/') { + token[0] = 0; + return ecs_parse_ws_eol(ptr + 2); + } + } + + ecs_parser_error(name, expr, ptr - expr, + "missing */ for multiline comment"); + return NULL; + } + } + + ecs_expr_oper_t op; + if (ptr[0] == '(') { + token[0] = '('; + token[1] = 0; + return ptr + 1; + } else if (ptr[0] != '-') { + const char *tptr = flecs_str_to_expr_oper(ptr, &op); + if (tptr) { + ecs_os_strncpy(token, ptr, tptr - ptr); + return tptr; + } + } + + while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr, 0))) { + if (ptr[0] == '|' && ptr[1] != '|') { + token_ptr = &token_ptr[ecs_os_strlen(token_ptr)]; + token_ptr[0] = '|'; + token_ptr[1] = '\0'; + token_ptr ++; + ptr ++; + } else { + break; + } + } + + return ptr; +} + +static +const char* flecs_parse_multiline_string( + ecs_meta_cursor_t *cur, + const char *name, + const char *expr, + const char *ptr) +{ + /* Multiline string */ + ecs_strbuf_t str = ECS_STRBUF_INIT; + char ch; + while ((ch = ptr[0]) && (ch != '`')) { + if (ch == '\\' && ptr[1] == '`') { + ch = '`'; + ptr ++; + } + ecs_strbuf_appendch(&str, ch); + ptr ++; + } + + if (ch != '`') { + ecs_parser_error(name, expr, ptr - expr, + "missing '`' to close multiline string"); + goto error; + } + char *strval = ecs_strbuf_get(&str); + if (ecs_meta_set_string(cur, strval) != 0) { + goto error; + } + ecs_os_free(strval); + + return ptr + 1; +error: + return NULL; +} + +static +bool flecs_parse_is_float( + const char *ptr) +{ + ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); + char ch; + while ((ch = (++ptr)[0])) { + if (ch == '.' || ch == 'e') { + return true; + } + if (!isdigit(ch)) { + return false; + } + } + return false; +} + +/* Attempt to resolve variable dotexpression to value (foo.bar) */ +static +ecs_value_t flecs_dotresolve_var( + ecs_world_t *world, + ecs_vars_t *vars, + char *token) +{ + char *dot = strchr(token, '.'); + if (!dot) { + return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; + } + + dot[0] = '\0'; + + const ecs_expr_var_t *var = ecs_vars_lookup(vars, token); + if (!var) { + return (ecs_value_t){0}; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor( + world, var->value.type, var->value.ptr); + ecs_meta_push(&cur); + if (ecs_meta_dotmember(&cur, dot + 1) != 0) { + return (ecs_value_t){0}; + } + + return (ecs_value_t){ + .ptr = ecs_meta_get_ptr(&cur), + .type = ecs_meta_get_type(&cur) + }; +} + +static +int flecs_meta_call( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_meta_cursor_t *cur, + const char *function) +{ + ecs_entity_t type = ecs_meta_get_type(cur); + void *value_ptr = ecs_meta_get_ptr(cur); + + if (!ecs_os_strcmp(function, "parent")) { + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "parent() can only be called on entity"); + return -1; + } + + *(ecs_entity_t*)value_ptr = ecs_get_parent( + world, *(ecs_entity_t*)value_ptr); + } else if (!ecs_os_strcmp(function, "name")) { + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "name() can only be called on entity"); + return -1; + } + + char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); + *result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); + *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); + } else if (!ecs_os_strcmp(function, "doc_name")) { +#ifdef FLECS_DOC + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "name() can only be called on entity"); + return -1; + } + + char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); + *result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); + *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); +#else + ecs_parser_error(name, expr, ptr - expr, + "doc_name() is not available without FLECS_DOC addon"); + return -1; +#endif + } else { + ecs_parser_error(name, expr, ptr - expr, + "unknown function '%s'", function); + return -1; + } + + return 0; +} + +/* Determine the type of an expression from the first character(s). This allows + * us to initialize a storage for a type if none was provided. */ +static +ecs_entity_t flecs_parse_discover_type( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_entity_t input_type, + const ecs_parse_expr_desc_t *desc) +{ + /* String literal */ + if (ptr[0] == '"' || ptr[0] == '`') { + if (input_type == ecs_id(ecs_char_t)) { + return input_type; + } + return ecs_id(ecs_string_t); + } + + /* Negative number literal */ + if (ptr[0] == '-') { + if (!isdigit(ptr[1])) { + ecs_parser_error(name, expr, ptr - expr, "invalid literal"); + return 0; + } + if (flecs_parse_is_float(ptr + 1)) { + return ecs_id(ecs_f64_t); + } else { + return ecs_id(ecs_i64_t); + } + } + + /* Positive number literal */ + if (isdigit(ptr[0])) { + if (flecs_parse_is_float(ptr)) { + return ecs_id(ecs_f64_t); + } else { + return ecs_id(ecs_u64_t); + } + } + + /* Variable */ + if (ptr[0] == '$') { + if (!desc || !desc->vars) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable (no variable scope)"); + return 0; + } + char token[ECS_MAX_TOKEN_SIZE]; + if (ecs_parse_expr_token(name, expr, &ptr[1], token) == NULL) { + return 0; + } + + const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); + if (!var) { + ecs_size_t len = ecs_os_strlen(token); + if (ptr[len + 1] == '(') { + while ((len > 0) && (token[len] != '.')) { + len --; + } + if (token[len] == '.') { + token[len] = '\0'; + } + } + + ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); + if (v.type) { + return v.type; + } + + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", token); + return 0; + } + return var->value.type; + } + + /* Boolean */ + if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { + if (!isalpha(ptr[4]) && ptr[4] != '_') { + return ecs_id(ecs_bool_t); + } + } + if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { + if (!isalpha(ptr[5]) && ptr[5] != '_') { + return ecs_id(ecs_bool_t); + } + } + + /* Entity identifier */ + if (isalpha(ptr[0])) { + if (!input_type) { /* Identifier could also be enum/bitmask constant */ + return ecs_id(ecs_entity_t); + } + } + + /* If no default type was provided we can't automatically deduce the type of + * composite/collection expressions. */ + if (!input_type) { + if (ptr[0] == '{') { + ecs_parser_error(name, expr, ptr - expr, + "unknown type for composite literal"); + return 0; + } + + if (ptr[0] == '[') { + ecs_parser_error(name, expr, ptr - expr, + "unknown type for collection literal"); + return 0; + } + + ecs_parser_error(name, expr, ptr - expr, "invalid expression"); + } + + return input_type; +} + +/* Normalize types to their largest representation. + * Rather than taking the original type of a value, use the largest + * representation of the type so we don't have to worry about overflowing the + * original type in the operation. */ +static +ecs_entity_t flecs_largest_type( + const EcsPrimitive *type) +{ + switch(type->kind) { + case EcsBool: return ecs_id(ecs_bool_t); + case EcsChar: return ecs_id(ecs_char_t); + case EcsByte: return ecs_id(ecs_u8_t); + case EcsU8: return ecs_id(ecs_u64_t); + case EcsU16: return ecs_id(ecs_u64_t); + case EcsU32: return ecs_id(ecs_u64_t); + case EcsU64: return ecs_id(ecs_u64_t); + case EcsI8: return ecs_id(ecs_i64_t); + case EcsI16: return ecs_id(ecs_i64_t); + case EcsI32: return ecs_id(ecs_i64_t); + case EcsI64: return ecs_id(ecs_i64_t); + case EcsF32: return ecs_id(ecs_f64_t); + case EcsF64: return ecs_id(ecs_f64_t); + case EcsUPtr: return ecs_id(ecs_u64_t); + case EcsIPtr: return ecs_id(ecs_i64_t); + case EcsString: return ecs_id(ecs_string_t); + case EcsEntity: return ecs_id(ecs_entity_t); + case EcsId: return ecs_id(ecs_id_t); + default: ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return 0; +} + +/** Test if a normalized type can promote to another type in an expression */ +static +bool flecs_is_type_number( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + +static +bool flecs_oper_valid_for_type( + ecs_entity_t type, + ecs_expr_oper_t op) +{ + switch(op) { + case EcsAdd: + case EcsSub: + case EcsMul: + case EcsDiv: + return flecs_is_type_number(type); + case EcsCondEq: + case EcsCondNeq: + case EcsCondAnd: + case EcsCondOr: + case EcsCondGt: + case EcsCondGtEq: + case EcsCondLt: + case EcsCondLtEq: + return flecs_is_type_number(type) || + (type == ecs_id(ecs_bool_t)) || + (type == ecs_id(ecs_char_t)) || + (type == ecs_id(ecs_entity_t)); + case EcsShiftLeft: + case EcsShiftRight: + return (type == ecs_id(ecs_u64_t)); + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + return false; + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +/** Promote type to most expressive (f64 > i64 > u64) */ +static +ecs_entity_t flecs_promote_type( + ecs_entity_t type, + ecs_entity_t promote_to) +{ + if (type == ecs_id(ecs_u64_t)) { + return promote_to; + } + if (promote_to == ecs_id(ecs_u64_t)) { + return type; + } + if (type == ecs_id(ecs_f64_t)) { + return type; + } + if (promote_to == ecs_id(ecs_f64_t)) { + return promote_to; + } + return ecs_id(ecs_i64_t); +} + +static +int flecs_oper_precedence( + ecs_expr_oper_t left, + ecs_expr_oper_t right) +{ + return (left > right) - (left < right); +} + +static +void flecs_value_cast( + ecs_world_t *world, + ecs_value_stack_t *stack, + ecs_value_t *value, + ecs_entity_t type) +{ + if (value->type == type) { + return; + } + + ecs_value_t result; + result.type = type; + result.ptr = flecs_expr_value_new(stack, type); + + if (value->ptr) { + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); + ecs_meta_set_value(&cur, value); + } + + *value = result; +} + +static +bool flecs_expr_op_is_equality( + ecs_expr_oper_t op) +{ + switch(op) { + case EcsCondEq: + case EcsCondNeq: + case EcsCondGt: + case EcsCondGtEq: + case EcsCondLt: + case EcsCondLtEq: + return true; + case EcsCondAnd: + case EcsCondOr: + case EcsShiftLeft: + case EcsShiftRight: + case EcsAdd: + case EcsSub: + case EcsMul: + case EcsDiv: + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + return false; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } +error: + return false; +} + +static +ecs_entity_t flecs_binary_expr_type( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_value_t *lvalue, + ecs_value_t *rvalue, + ecs_expr_oper_t op, + ecs_entity_t *operand_type_out) +{ + ecs_entity_t result_type = 0, operand_type = 0; + + switch(op) { + case EcsDiv: + /* Result type of a division is always a float */ + *operand_type_out = ecs_id(ecs_f64_t); + return ecs_id(ecs_f64_t); + case EcsCondAnd: + case EcsCondOr: + /* Result type of a condition operator is always a bool */ + *operand_type_out = ecs_id(ecs_bool_t); + return ecs_id(ecs_bool_t); + case EcsCondEq: + case EcsCondNeq: + case EcsCondGt: + case EcsCondGtEq: + case EcsCondLt: + case EcsCondLtEq: + /* Result type of equality operator is always bool, but operand types + * should not be casted to bool */ + result_type = ecs_id(ecs_bool_t); + break; + case EcsShiftLeft: + case EcsShiftRight: + case EcsAdd: + case EcsSub: + case EcsMul: + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } + + /* Result type for arithmetic operators is determined by operands */ + const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); + const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); + if (!ltype_ptr || !rtype_ptr) { + char *lname = ecs_get_fullpath(world, lvalue->type); + char *rname = ecs_get_fullpath(world, rvalue->type); + ecs_parser_error(name, expr, ptr - expr, + "invalid non-primitive type in binary expression (%s, %s)", + lname, rname); + ecs_os_free(lname); + ecs_os_free(rname); + return 0; + } + + ecs_entity_t ltype = flecs_largest_type(ltype_ptr); + ecs_entity_t rtype = flecs_largest_type(rtype_ptr); + if (ltype == rtype) { + operand_type = ltype; + goto done; + } + + if (flecs_expr_op_is_equality(op)) { + ecs_parser_error(name, expr, ptr - expr, + "mismatching types in equality expression"); + return 0; + } + + if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { + ecs_parser_error(name, expr, ptr - expr, + "incompatible types in binary expression"); + return 0; + } + + operand_type = flecs_promote_type(ltype, rtype); + +done: + if (op == EcsSub && operand_type == ecs_id(ecs_u64_t)) { + /* Result of subtracting two unsigned ints can be negative */ + operand_type = ecs_id(ecs_i64_t); + } + + if (!result_type) { + result_type = operand_type; + } + + *operand_type_out = operand_type; + return result_type; +error: + return 0; +} + +/* Macro's to let the compiler do the operations & conversion work for us */ + +#define ECS_VALUE_GET(value, T) (*(T*)value->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ecs_parser_error(name, expr, ptr - expr, "unsupported operator for floating point");\ + return -1;\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +static +int flecs_binary_expr_do( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_value_t *lvalue, + ecs_value_t *rvalue, + ecs_value_t *result, + ecs_expr_oper_t op) +{ + /* Find expression type */ + ecs_entity_t operand_type, type = flecs_binary_expr_type( + world, name, expr, ptr, lvalue, rvalue, op, &operand_type); + if (!type) { + goto error; + } + + if (!flecs_oper_valid_for_type(type, op)) { + ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); + goto error; + } + + flecs_value_cast(world, stack, lvalue, operand_type); + flecs_value_cast(world, stack, rvalue, operand_type); + + ecs_value_t *storage = result; + ecs_value_t tmp_storage = {0}; + if (result->type != type) { + storage = &tmp_storage; + storage->type = type; + storage->ptr = flecs_expr_value_new(stack, type); + } + + switch(op) { + case EcsAdd: + ECS_BINARY_OP(lvalue, rvalue, storage, +); + break; + case EcsSub: + ECS_BINARY_OP(lvalue, rvalue, storage, -); + break; + case EcsMul: + ECS_BINARY_OP(lvalue, rvalue, storage, *); + break; + case EcsDiv: + ECS_BINARY_OP(lvalue, rvalue, storage, /); + break; + case EcsCondEq: + ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, ==); + break; + case EcsCondNeq: + ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, !=); + break; + case EcsCondGt: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); + break; + case EcsCondGtEq: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); + break; + case EcsCondLt: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); + break; + case EcsCondLtEq: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); + break; + case EcsCondAnd: + ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); + break; + case EcsCondOr: + ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); + break; + case EcsShiftLeft: + ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); + break; + case EcsShiftRight: + ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); + break; + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); + goto error; + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + + if (storage->ptr != result->ptr) { + if (!result->ptr) { + *result = *storage; + } else { + ecs_meta_cursor_t cur = ecs_meta_cursor(world, + result->type, result->ptr); + ecs_meta_set_value(&cur, storage); + } + } + + return 0; +error: + return -1; +} + +static +const char* flecs_binary_expr_parse( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_value_t *lvalue, + ecs_value_t *result, + ecs_expr_oper_t left_op, + const ecs_parse_expr_desc_t *desc) +{ + ecs_entity_t result_type = result->type; + do { + ecs_expr_oper_t op; + ptr = flecs_str_to_expr_oper(ptr, &op); + if (!ptr) { + ecs_parser_error(name, expr, ptr - expr, "invalid operator"); + return NULL; + } + + ptr = ecs_parse_ws_eol(ptr); + + ecs_value_t rvalue = {0}; + const char *rptr = flecs_parse_expr(world, stack, ptr, &rvalue, op, desc); + if (!rptr) { + return NULL; + } + + if (flecs_binary_expr_do(world, stack, name, expr, ptr, + lvalue, &rvalue, result, op)) + { + return NULL; + } + + ptr = rptr; + + ecs_expr_oper_t right_op; + flecs_str_to_expr_oper(rptr, &right_op); + if (right_op > left_op) { + if (result_type) { + /* If result was initialized, preserve its value */ + lvalue->type = result->type; + lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); + ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); + continue; + } else { + /* Otherwise move result to lvalue */ + *lvalue = *result; + ecs_os_zeromem(result); + continue; + } + } + + break; + } while (true); + + return ptr; +} + +static +const char* flecs_funccall_parse( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + char *token, + ecs_meta_cursor_t *cur, + ecs_value_t *value, + bool isvar, + const ecs_parse_expr_desc_t *desc) +{ + char *sep = strrchr(token, '.'); + if (!sep) { + ecs_parser_error(name, expr, ptr - expr, + "missing object for function call '%s'", token); + return NULL; + } + + sep[0] = '\0'; + const char *function = sep + 1; + + if (!isvar) { + if (ecs_meta_set_string(cur, token) != 0) { + goto error; + } + } else { + const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); + ecs_meta_set_value(cur, &var->value); + } + + do { + if (!function[0]) { + ecs_parser_error(name, expr, ptr - expr, + "missing function name for function call '%s'", token); + return NULL; + } + + if (flecs_meta_call(world, stack, name, expr, ptr, cur, + function) != 0) + { + goto error; + } + + ecs_entity_t type = ecs_meta_get_type(cur); + value->ptr = ecs_meta_get_ptr(cur); + value->type = type; + + ptr += 2; + if (ptr[0] != '.') { + break; + } + + ptr ++; + char *paren = strchr(ptr, '('); + if (!paren) { + break; + } + + ecs_size_t len = flecs_ito(int32_t, paren - ptr); + if (len >= ECS_MAX_TOKEN_SIZE) { + ecs_parser_error(name, expr, ptr - expr, + "token exceeds maximum token size"); + goto error; + } + + ecs_os_strncpy(token, ptr, len); + token[len] = '\0'; + function = token; + + ptr = paren; + } while (true); + + return ptr; +error: + return NULL; +} + +static +const char* flecs_parse_expr( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *ptr, + ecs_value_t *value, + ecs_expr_oper_t left_op, + const ecs_parse_expr_desc_t *desc) +{ + ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); + char token[ECS_MAX_TOKEN_SIZE]; + int depth = 0; + ecs_value_t result = {0}; + ecs_meta_cursor_t cur = {0}; + const char *name = desc ? desc->name : NULL; + const char *expr = desc ? desc->expr : NULL; + token[0] = '\0'; + expr = expr ? expr : ptr; + + ptr = ecs_parse_ws_eol(ptr); + + /* Check for postfix operators */ + ecs_expr_oper_t unary_op = EcsExprOperUnknown; + if (ptr[0] == '-' && !isdigit(ptr[1])) { + unary_op = EcsMin; + ptr = ecs_parse_ws_eol(ptr + 1); + } + + /* Initialize storage and cursor. If expression starts with a '(' storage + * will be initialized by a nested expression */ + if (ptr[0] != '(') { + ecs_entity_t type = flecs_parse_discover_type( + world, name, expr, ptr, value->type, desc); + if (!type) { + return NULL; + } + + result.type = type; + if (type != value->type) { + result.ptr = flecs_expr_value_new(stack, type); + } else { + result.ptr = value->ptr; + } + + cur = ecs_meta_cursor(world, result.type, result.ptr); + if (!cur.valid) { + return NULL; + } + + cur.lookup_action = desc ? desc->lookup_action : NULL; + cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; + } + + /* Loop that parses all values in a value scope */ + while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + /* Used to track of the result of the parsed token can be used as the + * lvalue for a binary expression */ + bool is_lvalue = false; + bool newline = false; + + if (!token[0]) { + /* Comment */ + continue; + } + + if (!ecs_os_strcmp(token, "(")) { + ecs_value_t temp_result, *out; + if (!depth) { + out = &result; + } else { + temp_result.type = ecs_meta_get_type(&cur); + temp_result.ptr = ecs_meta_get_ptr(&cur); + out = &temp_result; + } + + /* Parenthesis, parse nested expression */ + ptr = flecs_parse_expr(world, stack, ptr, out, EcsLeftParen, desc); + if (ptr[0] != ')') { + ecs_parser_error(name, expr, ptr - expr, + "missing closing parenthesis"); + return NULL; + } + ptr = ecs_parse_ws(ptr + 1); + is_lvalue = true; + + } else if (!ecs_os_strcmp(token, "{")) { + /* Parse nested value scope */ + ecs_entity_t scope_type = ecs_meta_get_type(&cur); + + depth ++; /* Keep track of depth so we know when parsing is done */ + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (ecs_meta_is_collection(&cur)) { + char *path = ecs_get_fullpath(world, scope_type); + ecs_parser_error(name, expr, ptr - expr, + "expected '[' for collection type '%s'", path); + ecs_os_free(path); + return NULL; + } + } + + else if (!ecs_os_strcmp(token, "}")) { + depth --; + + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected ']'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } + + else if (!ecs_os_strcmp(token, "[")) { + /* Open collection value scope */ + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "unexpected '['"); + return NULL; + } + } + + else if (!ecs_os_strcmp(token, "]")) { + depth --; + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } + + else if (!ecs_os_strcmp(token, "-")) { + if (unary_op != EcsExprOperUnknown) { + ecs_parser_error(name, expr, ptr - expr, + "unexpected unary operator"); + return NULL; + } + unary_op = EcsMin; + } + + else if (!ecs_os_strcmp(token, ",")) { + /* Move to next field */ + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } + + else if (!ecs_os_strcmp(token, "null")) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + + is_lvalue = true; + } + + else if (token[0] == '\"') { + /* Regular string */ + if (ecs_meta_set_string_literal(&cur, token) != 0) { + goto error; + } + + is_lvalue = true; + } + + else if (!ecs_os_strcmp(token, "`")) { + /* Multiline string */ + if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { + goto error; + } + + is_lvalue = true; + + } else if (token[0] == '$') { + /* Variable */ + if (!desc || !desc->vars) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s' (no variable scope)", token); + return NULL; + } + + if (!token[1]) { + /* Empty name means default to assigned member */ + const char *member = ecs_meta_get_member(&cur); + if (!member) { + ecs_parser_error(name, expr, ptr - expr, + "invalid default variable outside member assignment"); + return NULL; + } + ecs_os_strcpy(&token[1], member); + } + + const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, &token[1]); + if (!var) { + if (ptr[0] == '(') { + /* Function */ + ptr = flecs_funccall_parse(world, stack, name, expr, ptr, + &token[1], &cur, &result, true, desc); + if (!ptr) { + goto error; + } + } else { + ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); + if (!v.ptr) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", token); + return NULL; + } else { + ecs_meta_set_value(&cur, &v); + } + } + } else { + ecs_meta_set_value(&cur, &var->value); + } + is_lvalue = true; + + } else { + const char *tptr = ecs_parse_ws(ptr); + for (; ptr != tptr; ptr ++) { + if (ptr[0] == '\n') { + newline = true; + } + } + + if (ptr[0] == ':') { + /* Member assignment */ + ptr ++; + if (ecs_meta_dotmember(&cur, token) != 0) { + goto error; + } + } else if (ptr[0] == '(') { + /* Function */ + ptr = flecs_funccall_parse(world, stack, name, expr, ptr, + token, &cur, &result, false, desc); + if (!ptr) { + goto error; + } + } else { + if (ecs_meta_set_string(&cur, token) != 0) { + goto error; + } + } + + is_lvalue = true; + } + + /* If lvalue was parsed, apply operators. Expressions cannot start + * directly after a newline character. */ + if (is_lvalue && !newline) { + if (unary_op != EcsExprOperUnknown) { + if (unary_op == EcsMin) { + int64_t v = -1; + ecs_value_t lvalue = {.type = ecs_id(ecs_i64_t), .ptr = &v}; + ecs_value_t *out, rvalue, temp_out = {0}; + + if (!depth) { + rvalue = result; + ecs_os_zeromem(&result); + out = &result; + } else { + ecs_entity_t cur_type = ecs_meta_get_type(&cur); + void *cur_ptr = ecs_meta_get_ptr(&cur); + rvalue.type = cur_type; + rvalue.ptr = cur_ptr; + temp_out.type = cur_type; + temp_out.ptr = cur_ptr; + out = &temp_out; + } + + flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, + &rvalue, out, EcsMul); + } + unary_op = 0; + } + + ecs_expr_oper_t right_op; + flecs_str_to_expr_oper(ptr, &right_op); + if (right_op) { + /* This is a binary expression, test precedence to determine if + * it should be evaluated here */ + if (flecs_oper_precedence(left_op, right_op) < 0) { + ecs_value_t lvalue; + ecs_value_t *op_result = &result; + ecs_value_t temp_storage; + if (!depth) { + /* Root level value, move result to lvalue storage */ + lvalue = result; + ecs_os_zeromem(&result); + } else { + /* Not a root level value. Move the parsed lvalue to a + * temporary storage, and initialize the result value + * for the binary operation with the current cursor */ + ecs_entity_t cur_type = ecs_meta_get_type(&cur); + void *cur_ptr = ecs_meta_get_ptr(&cur); + lvalue.type = cur_type; + lvalue.ptr = flecs_expr_value_new(stack, cur_type); + ecs_value_copy(world, cur_type, lvalue.ptr, cur_ptr); + temp_storage.type = cur_type; + temp_storage.ptr = cur_ptr; + op_result = &temp_storage; + } + + /* Do the binary expression */ + ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, + &lvalue, op_result, left_op, desc); + if (!ptr) { + return NULL; + } + } + } + } + + if (!depth) { + /* Reached the end of the root scope */ + break; + } + + ptr = ecs_parse_ws_eol(ptr); + } + + if (!value->ptr) { + value->type = result.type; + value->ptr = flecs_expr_value_new(stack, result.type); + } + + if (value->ptr != result.ptr) { + cur = ecs_meta_cursor(world, value->type, value->ptr); + ecs_meta_set_value(&cur, &result); + } + + ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + return ptr; +error: + return NULL; +} + +const char* ecs_parse_expr( + ecs_world_t *world, + const char *ptr, + ecs_value_t *value, + const ecs_parse_expr_desc_t *desc) +{ + /* Prepare storage for temporary values */ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_value_stack_t stack; + stack.count = 0; + stack.stage = stage; + stack.stack = &stage->allocators.deser_stack; + stack.cursor = flecs_stack_get_cursor(stack.stack); + + /* Parse expression */ + bool storage_provided = value->ptr != NULL; + ptr = flecs_parse_expr(world, &stack, ptr, value, EcsExprOperUnknown, desc); + + /* If no result value was provided, allocate one as we can't return a + * pointer to a temporary storage */ + if (!storage_provided && value->ptr) { + ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); + void *temp_storage = value->ptr; + value->ptr = ecs_value_new(world, value->type); + ecs_value_move_ctor(world, value->type, value->ptr, temp_storage); + } + + /* Cleanup temporary values */ + int i; + for (i = 0; i < stack.count; i ++) { + const ecs_type_info_t *ti = stack.values[i].ti; + ecs_assert(ti->hooks.dtor != NULL, ECS_INTERNAL_ERROR, NULL); + ti->hooks.dtor(stack.values[i].ptr, 1, ti); + } + flecs_stack_restore_cursor(stack.stack, stack.cursor); + + return ptr; +} + +#endif + +/** + * @file expr/serialize.c + * @brief Serialize (component) values to flecs string format. + */ + + +#ifdef FLECS_EXPR + +static +int flecs_expr_ser_type( + const ecs_world_t *world, + const ecs_vec_t *ser, + const void *base, + ecs_strbuf_t *str, + bool is_expr); + +static +int flecs_expr_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str, + int32_t in_array, + bool is_expr); + +static +int flecs_expr_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str, + bool is_expr); + +static +ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { + return kind - EcsOpPrimitive; +} + +/* Serialize a primitive value */ +static +int flecs_expr_ser_primitive( + const ecs_world_t *world, + ecs_primitive_kind_t kind, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + switch(kind) { + case EcsBool: + if (*(const bool*)base) { + ecs_strbuf_appendlit(str, "true"); + } else { + ecs_strbuf_appendlit(str, "false"); + } + break; + case EcsChar: { + char chbuf[3]; + char ch = *(const char*)base; + if (ch) { + ecs_chresc(chbuf, *(const char*)base, '"'); + if (is_expr) ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstr(str, chbuf); + if (is_expr) ecs_strbuf_appendch(str, '"'); + } else { + ecs_strbuf_appendch(str, '0'); + } + break; + } + case EcsByte: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); + break; + case EcsU8: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); + break; + case EcsU16: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint16_t*)base)); + break; + case EcsU32: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint32_t*)base)); + break; + case EcsU64: + ecs_strbuf_append(str, "%llu", *(const uint64_t*)base); + break; + case EcsI8: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int8_t*)base)); + break; + case EcsI16: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int16_t*)base)); + break; + case EcsI32: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int32_t*)base)); + break; + case EcsI64: + ecs_strbuf_appendint(str, *(const int64_t*)base); + break; + case EcsF32: + ecs_strbuf_appendflt(str, (double)*(const float*)base, 0); + break; + case EcsF64: + ecs_strbuf_appendflt(str, *(const double*)base, 0); + break; + case EcsIPtr: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const intptr_t*)base)); + break; + case EcsUPtr: + ecs_strbuf_append(str, "%u", *(const uintptr_t*)base); + break; + case EcsString: { + const char *value = *ECS_CONST_CAST(const char**, base); + if (value) { + if (!is_expr) { + ecs_strbuf_appendstr(str, value); + } else { + ecs_size_t length = ecs_stresc(NULL, 0, '"', value); + if (length == ecs_os_strlen(value)) { + ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstrn(str, value, length); + ecs_strbuf_appendch(str, '"'); + } else { + char *out = ecs_os_malloc(length + 3); + ecs_stresc(out + 1, length, '"', value); + out[0] = '"'; + out[length + 1] = '"'; + out[length + 2] = '\0'; + ecs_strbuf_appendstr(str, out); + ecs_os_free(out); + } + } + } else { + ecs_strbuf_appendlit(str, "null"); + } + break; + } + case EcsEntity: { + ecs_entity_t e = *(const ecs_entity_t*)base; + if (!e) { + ecs_strbuf_appendch(str, '0'); + } else { + ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str); + } + break; + } + case EcsId: { + ecs_id_t id = *(const ecs_id_t*)base; + if (!id) { + ecs_strbuf_appendch(str, '0'); + } else { + ecs_id_str_buf(world, id, str); + } + break; + } + default: + ecs_err("invalid primitive kind"); + return -1; + } + + return 0; +} + +/* Serialize enumeration */ +static +int flecs_expr_ser_enum( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); + ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t val = *(const int32_t*)base; + + /* Enumeration constants are stored in a map that is keyed on the + * enumeration value. */ + ecs_enum_constant_t *c = ecs_map_get_deref(&enum_type->constants, + ecs_enum_constant_t, (ecs_map_key_t)val); + if (!c) { + char *path = ecs_get_fullpath(world, op->type); + ecs_err("value %d is not valid for enum type '%s'", val, path); + ecs_os_free(path); + goto error; + } + + ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant)); + + return 0; +error: + return -1; +} + +/* Serialize bitmask */ +static +int flecs_expr_ser_bitmask( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); + ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); + uint32_t value = *(const uint32_t*)ptr; + + ecs_strbuf_list_push(str, "", "|"); + + /* Multiple flags can be set at a given time. Iterate through all the flags + * and append the ones that are set. */ + ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); + int count = 0; + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *c = ecs_map_ptr(&it); + ecs_map_key_t key = ecs_map_key(&it); + if ((value & key) == key) { + ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant)); + count ++; + value -= (uint32_t)key; + } + } + + if (value != 0) { + /* All bits must have been matched by a constant */ + char *path = ecs_get_fullpath(world, op->type); + ecs_err( + "value for bitmask %s contains bits (%u) that cannot be mapped to constant", + path, value); + ecs_os_free(path); + goto error; + } + + if (!count) { + ecs_strbuf_list_appendstr(str, "0"); + } + + ecs_strbuf_list_pop(str, ""); + + return 0; +error: + return -1; +} + +/* Serialize elements of a contiguous array */ +static +int expr_ser_elements( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + int32_t elem_count, + int32_t elem_size, + ecs_strbuf_t *str, + bool is_array) +{ + ecs_strbuf_list_push(str, "[", ", "); + + const void *ptr = base; + + int i; + for (i = 0; i < elem_count; i ++) { + ecs_strbuf_list_next(str); + if (flecs_expr_ser_type_ops( + world, ops, op_count, ptr, str, is_array, true)) + { + return -1; + } + ptr = ECS_OFFSET(ptr, elem_size); + } + + ecs_strbuf_list_pop(str, "]"); + + return 0; +} + +static +int expr_ser_type_elements( + const ecs_world_t *world, + ecs_entity_t type, + const void *base, + int32_t elem_count, + ecs_strbuf_t *str, + bool is_array) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); + int32_t op_count = ecs_vec_count(&ser->ops); + return expr_ser_elements( + world, ops, op_count, base, elem_count, comp->size, str, is_array); +} + +/* Serialize array */ +static +int expr_ser_array( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + const EcsArray *a = ecs_get(world, op->type, EcsArray); + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + + return expr_ser_type_elements( + world, a->type, ptr, a->count, str, true); +} + +/* Serialize vector */ +static +int expr_ser_vector( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + const ecs_vec_t *value = base; + const EcsVector *v = ecs_get(world, op->type, EcsVector); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t count = ecs_vec_count(value); + void *array = ecs_vec_first(value); + + /* Serialize contiguous buffer of vector */ + return expr_ser_type_elements(world, v->type, array, count, str, false); +} + +/* Forward serialization to the different type kinds */ +static +int flecs_expr_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str, + bool is_expr) +{ + switch(op->kind) { + case EcsOpPush: + case EcsOpPop: + /* Should not be parsed as single op */ + ecs_throw(ECS_INVALID_PARAMETER, NULL); + break; + case EcsOpEnum: + if (flecs_expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpBitmask: + if (flecs_expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpArray: + if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpVector: + if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + case EcsOpString: + case EcsOpOpaque: + if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), + ECS_OFFSET(ptr, op->offset), str, is_expr)) + { + /* Unknown operation */ + ecs_err("unknown serializer operation kind (%d)", op->kind); + goto error; + } + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +/* Iterate over a slice of the type ops array */ +static +int flecs_expr_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str, + int32_t in_array, + bool is_expr) +{ + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; + + if (in_array <= 0) { + if (op->name) { + ecs_strbuf_list_next(str); + ecs_strbuf_append(str, "%s: ", op->name); + } + + int32_t elem_count = op->count; + if (elem_count > 1) { + /* Serialize inline array */ + if (expr_ser_elements(world, op, op->op_count, base, + elem_count, op->size, str, true)) + { + return -1; + } + + i += op->op_count - 1; + continue; + } + } + + switch(op->kind) { + case EcsOpPush: + ecs_strbuf_list_push(str, "{", ", "); + in_array --; + break; + case EcsOpPop: + ecs_strbuf_list_pop(str, "}"); + in_array ++; + break; + case EcsOpArray: + case EcsOpVector: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + case EcsOpString: + case EcsOpOpaque: + if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) { + goto error; + } + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + } + + return 0; +error: + return -1; +} + +/* Iterate over the type ops of a type */ +static +int flecs_expr_ser_type( + const ecs_world_t *world, + const ecs_vec_t *v_ops, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); + int32_t count = ecs_vec_count(v_ops); + return flecs_expr_ser_type_ops(world, ops, count, base, str, 0, is_expr); +} + +int ecs_ptr_to_expr_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (ser == NULL) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize value for type '%s'", path); + ecs_os_free(path); + goto error; + } + + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) { + goto error; + } + + return 0; +error: + return -1; +} + +char* ecs_ptr_to_expr( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +int ecs_ptr_to_str_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (ser == NULL) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize value for type '%s'", path); + ecs_os_free(path); + goto error; + } + + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) { + goto error; + } + + return 0; +error: + return -1; +} + +char* ecs_ptr_to_str( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +int ecs_primitive_to_expr_buf( + const ecs_world_t *world, + ecs_primitive_kind_t kind, + const void *base, + ecs_strbuf_t *str) +{ + return flecs_expr_ser_primitive(world, kind, base, str, true); +} + +#endif + +/** + * @file expr/utils.c + * @brief String parsing utilities. + */ + + +#ifdef FLECS_EXPR + +#include + +char* ecs_chresc( + char *out, + char in, + char delimiter) +{ + char *bptr = out; + switch(in) { + case '\a': + *bptr++ = '\\'; + *bptr = 'a'; + break; + case '\b': + *bptr++ = '\\'; + *bptr = 'b'; + break; + case '\f': + *bptr++ = '\\'; + *bptr = 'f'; + break; + case '\n': + *bptr++ = '\\'; + *bptr = 'n'; + break; + case '\r': + *bptr++ = '\\'; + *bptr = 'r'; + break; + case '\t': + *bptr++ = '\\'; + *bptr = 't'; + break; + case '\v': + *bptr++ = '\\'; + *bptr = 'v'; + break; + case '\\': + *bptr++ = '\\'; + *bptr = '\\'; + break; + case '\033': + *bptr = '['; /* Used for terminal colors */ + break; + default: + if (in == delimiter) { + *bptr++ = '\\'; + *bptr = delimiter; + } else { + *bptr = in; + } + break; + } + + *(++bptr) = '\0'; + + return bptr; +} + +const char* ecs_chrparse( + const char *in, + char *out) +{ + const char *result = in + 1; + char ch; + + if (in[0] == '\\') { + result ++; + + switch(in[1]) { + case 'a': + ch = '\a'; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 'v': + ch = '\v'; + break; + case '\\': + ch = '\\'; + break; + case '"': + ch = '"'; + break; + case '0': + ch = '\0'; + break; + case ' ': + ch = ' '; + break; + case '$': + ch = '$'; + break; + default: + goto error; + } + } else { + ch = in[0]; + } + + if (out) { + *out = ch; + } + + return result; +error: + return NULL; +} + +ecs_size_t ecs_stresc( + char *out, + ecs_size_t n, + char delimiter, + const char *in) +{ + const char *ptr = in; + char ch, *bptr = out, buff[3]; + ecs_size_t written = 0; + while ((ch = *ptr++)) { + if ((written += (ecs_size_t)(ecs_chresc( + buff, ch, delimiter) - buff)) <= n) + { + /* If size != 0, an out buffer must be provided. */ + ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); + *bptr++ = buff[0]; + if ((ch = buff[1])) { + *bptr = ch; + bptr++; + } + } + } + + if (bptr) { + while (written < n) { + *bptr = '\0'; + bptr++; + written++; + } + } + return written; +error: + return 0; +} + +char* ecs_astresc( + char delimiter, + const char *in) +{ + if (!in) { + return NULL; + } + + ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in); + char *out = ecs_os_malloc_n(char, len + 1); + ecs_stresc(out, len, delimiter, in); + out[len] = '\0'; + return out; +} + +static +const char* flecs_parse_var_name( + const char *ptr, + char *token_out) +{ + char ch, *bptr = token_out; + + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } + + if (isalpha(ch) || isdigit(ch) || ch == '_') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + break; + } + } + + if (bptr == token_out) { + goto error; + } + + *bptr = '\0'; + + return ptr; +error: + return NULL; +} + +static +const char* flecs_parse_interpolated_str( + const char *ptr, + char *token_out) +{ + char ch, *bptr = token_out; + + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } + + if (ch == '\\') { + if (ptr[1] == '}') { + *bptr = '}'; + bptr ++; + ptr += 2; + continue; + } + } + + if (ch != '}') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + ptr ++; + break; + } + } + + if (bptr == token_out) { + goto error; + } + + *bptr = '\0'; + + return ptr; +error: + return NULL; +} + +char* ecs_interpolate_string( + ecs_world_t *world, + const char *str, + const ecs_vars_t *vars) +{ + char token[ECS_MAX_TOKEN_SIZE]; + ecs_strbuf_t result = ECS_STRBUF_INIT; + const char *ptr; + char ch; + + for(ptr = str; (ch = *ptr); ptr++) { + if (ch == '\\') { + ptr ++; + if (ptr[0] == '$') { + ecs_strbuf_appendch(&result, '$'); + continue; + } + if (ptr[0] == '\\') { + ecs_strbuf_appendch(&result, '\\'); + continue; + } + if (ptr[0] == '{') { + ecs_strbuf_appendch(&result, '{'); + continue; + } + if (ptr[0] == '}') { + ecs_strbuf_appendch(&result, '}'); + continue; + } + ptr --; + } + + if (ch == '$') { + ptr = flecs_parse_var_name(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid variable name '%s'", ptr); + goto error; + } + + ecs_expr_var_t *var = ecs_vars_lookup(vars, token); + if (!var) { + ecs_parser_error(NULL, str, ptr - str, + "unresolved variable '%s'", token); + goto error; + } + + if (ecs_ptr_to_str_buf( + world, var->value.type, var->value.ptr, &result)) + { + goto error; + } + + ptr --; + } else if (ch == '{') { + ptr = flecs_parse_interpolated_str(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid interpolated expression"); + goto error; + } + + ecs_parse_expr_desc_t expr_desc = { + .vars = ECS_CONST_CAST(ecs_vars_t*, vars) + }; + ecs_value_t expr_result = {0}; + if (!ecs_parse_expr(world, token, &expr_result, &expr_desc)) { + goto error; + } + + if (ecs_ptr_to_str_buf( + world, expr_result.type, expr_result.ptr, &result)) + { + goto error; + } + + ecs_value_free(world, expr_result.type, expr_result.ptr); + + ptr --; + } else { + ecs_strbuf_appendch(&result, ch); + } + } + + return ecs_strbuf_get(&result); +error: + return NULL; +} + +void ecs_iter_to_vars( + const ecs_iter_t *it, + ecs_vars_t *vars, + int offset) +{ + ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL); + + /* Set variable for $this */ + if (it->count) { + ecs_expr_var_t *var = ecs_vars_lookup(vars, "this"); + if (!var) { + ecs_value_t v = { + .ptr = &it->entities[offset], + .type = ecs_id(ecs_entity_t) + }; + var = ecs_vars_declare_w_value(vars, "this", &v); + var->owned = false; + } else { + var->value.ptr = &it->entities[offset]; + } + } + + /* Set variables for fields */ + { + int32_t i, field_count = it->field_count; + for (i = 0; i < field_count; i ++) { + ecs_size_t size = it->sizes[i]; + if (!size) { + continue; + } + + void *ptr = it->ptrs[i]; + if (!ptr) { + continue; + } + + ptr = ECS_OFFSET(ptr, offset * size); + + char name[16]; + ecs_os_sprintf(name, "%d", i + 1); + ecs_expr_var_t *var = ecs_vars_lookup(vars, name); + if (!var) { + ecs_value_t v = { .ptr = ptr, .type = it->ids[i] }; + var = ecs_vars_declare_w_value(vars, name, &v); + var->owned = false; + } else { + ecs_check(var->value.type == it->ids[i], + ECS_INVALID_PARAMETER, NULL); + var->value.ptr = ptr; + } + } + } + + /* Set variables for query variables */ + { + int32_t i, var_count = it->variable_count; + for (i = 1 /* skip this variable */ ; i < var_count; i ++) { + ecs_entity_t *e_ptr = NULL; + ecs_var_t *query_var = &it->variables[i]; + if (query_var->entity) { + e_ptr = &query_var->entity; + } else { + ecs_table_range_t *range = &query_var->range; + if (range->count == 1) { + ecs_entity_t *entities = range->table->data.entities.array; + e_ptr = &entities[range->offset]; + } + } + if (!e_ptr) { + continue; + } + + ecs_expr_var_t *var = ecs_vars_lookup(vars, it->variable_names[i]); + if (!var) { + ecs_value_t v = { .ptr = e_ptr, .type = ecs_id(ecs_entity_t) }; + var = ecs_vars_declare_w_value(vars, it->variable_names[i], &v); + var->owned = false; + } else { + ecs_check(var->value.type == ecs_id(ecs_entity_t), + ECS_INVALID_PARAMETER, NULL); + var->value.ptr = e_ptr; + } + } + } + +error: + return; +} + +#endif + +/** + * @file expr/vars.c + * @brief Utilities for variable substitution in flecs string expressions. + */ + + +#ifdef FLECS_EXPR + +static +void flecs_expr_var_scope_init( + ecs_world_t *world, + ecs_expr_var_scope_t *scope, + ecs_expr_var_scope_t *parent) +{ + flecs_name_index_init(&scope->var_index, &world->allocator); + ecs_vec_init_t(&world->allocator, &scope->vars, ecs_expr_var_t, 0); + scope->parent = parent; +} + +static +void flecs_expr_var_scope_fini( + ecs_world_t *world, + ecs_expr_var_scope_t *scope) +{ + ecs_vec_t *vars = &scope->vars; + int32_t i, count = vars->count; + for (i = 0; i < count; i++) { + ecs_expr_var_t *var = ecs_vec_get_t(vars, ecs_expr_var_t, i); + if (var->owned) { + ecs_value_free(world, var->value.type, var->value.ptr); + } + flecs_strfree(&world->allocator, var->name); + } + + ecs_vec_fini_t(&world->allocator, &scope->vars, ecs_expr_var_t); + flecs_name_index_fini(&scope->var_index); +} + +void ecs_vars_init( + ecs_world_t *world, + ecs_vars_t *vars) +{ + flecs_expr_var_scope_init(world, &vars->root, NULL); + vars->world = world; + vars->cur = &vars->root; +} + +void ecs_vars_fini( + ecs_vars_t *vars) +{ + ecs_expr_var_scope_t *cur = vars->cur, *next; + do { + next = cur->parent; + flecs_expr_var_scope_fini(vars->world, cur); + if (cur != &vars->root) { + flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, cur); + } else { + break; + } + } while ((cur = next)); +} + +void ecs_vars_push( + ecs_vars_t *vars) +{ + ecs_expr_var_scope_t *scope = flecs_calloc_t(&vars->world->allocator, + ecs_expr_var_scope_t); + flecs_expr_var_scope_init(vars->world, scope, vars->cur); + vars->cur = scope; +} + +int ecs_vars_pop( + ecs_vars_t *vars) +{ + ecs_expr_var_scope_t *scope = vars->cur; + ecs_check(scope != &vars->root, ECS_INVALID_OPERATION, NULL); + vars->cur = scope->parent; + flecs_expr_var_scope_fini(vars->world, scope); + flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, scope); + return 0; +error: + return 1; +} + +ecs_expr_var_t* ecs_vars_declare( + ecs_vars_t *vars, + const char *name, + ecs_entity_t type) +{ + ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_expr_var_scope_t *scope = vars->cur; + ecs_hashmap_t *var_index = &scope->var_index; + + if (flecs_name_index_find(var_index, name, 0, 0) != 0) { + ecs_err("variable %s redeclared", name); + goto error; + } + + ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, + &scope->vars, ecs_expr_var_t); + + var->value.ptr = ecs_value_new(vars->world, type); + if (!var->value.ptr) { + goto error; + } + var->value.type = type; + var->name = flecs_strdup(&vars->world->allocator, name); + var->owned = true; + + flecs_name_index_ensure(var_index, + flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); + return var; +error: + return NULL; +} + +ecs_expr_var_t* ecs_vars_declare_w_value( + ecs_vars_t *vars, + const char *name, + ecs_value_t *value) +{ + ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_var_scope_t *scope = vars->cur; + ecs_hashmap_t *var_index = &scope->var_index; + + if (flecs_name_index_find(var_index, name, 0, 0) != 0) { + ecs_err("variable %s redeclared", name); + ecs_value_free(vars->world, value->type, value->ptr); + goto error; + } + + ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, + &scope->vars, ecs_expr_var_t); + var->value = *value; + var->name = flecs_strdup(&vars->world->allocator, name); + var->owned = true; + value->ptr = NULL; /* Take ownership, prevent double free */ + + flecs_name_index_ensure(var_index, + flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); + return var; +error: + return NULL; +} + +static +ecs_expr_var_t* flecs_vars_scope_lookup( + ecs_expr_var_scope_t *scope, + const char *name) +{ + uint64_t var_id = flecs_name_index_find(&scope->var_index, name, 0, 0); + if (var_id == 0) { + if (scope->parent) { + return flecs_vars_scope_lookup(scope->parent, name); + } + return NULL; + } + + return ecs_vec_get_t(&scope->vars, ecs_expr_var_t, + flecs_uto(int32_t, var_id - 1)); +} + +ecs_expr_var_t* ecs_vars_lookup( + const ecs_vars_t *vars, + const char *name) +{ + return flecs_vars_scope_lookup(vars->cur, name); +} + +#endif + +/** + * @file json/deserialize.c + * @brief Deserialize JSON strings into (component) values. + */ + +#include + +#ifdef FLECS_JSON + +static +const char* flecs_json_parse_path( + const ecs_world_t *world, + const char *json, + char *token, + ecs_entity_t *out, + const ecs_from_json_desc_t *desc) +{ + json = flecs_json_expect(json, JsonString, token, desc); + if (!json) { + goto error; + } + + ecs_entity_t result = ecs_lookup_fullpath(world, token); + if (!result) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "unresolved identifier '%s'", token); + goto error; + } + + *out = result; + + return json; +error: + return NULL; +} + +const char* ecs_ptr_from_json( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr, + const char *json, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + char token_buffer[ECS_MAX_TOKEN_SIZE], t_lah[ECS_MAX_TOKEN_SIZE]; + char *token = token_buffer; + int depth = 0; + + const char *name = NULL; + const char *expr = NULL; + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); + if (cur.valid == false) { + return NULL; + } + + if (desc) { + name = desc->name; + expr = desc->expr; + cur.lookup_action = desc->lookup_action; + cur.lookup_ctx = desc->lookup_ctx; + } + + while ((json = flecs_json_parse(json, &token_kind, token))) { + if (token_kind == JsonLargeString) { + ecs_strbuf_t large_token = ECS_STRBUF_INIT; + json = flecs_json_parse_large_string(json, &large_token); + if (!json) { + break; + } + + token = ecs_strbuf_get(&large_token); + token_kind = JsonString; + } + + if (token_kind == JsonObjectOpen) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected '['"); + return NULL; + } + } else if (token_kind == JsonObjectClose) { + depth --; + + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected ']'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonArrayOpen) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected '{'"); + return NULL; + } + } else if (token_kind == JsonArrayClose) { + depth --; + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected '}'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonComma) { + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonNull) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonString) { + const char *lah = flecs_json_parse( + json, &token_kind, t_lah); + if (token_kind == JsonColon) { + /* Member assignment */ + json = lah; + if (ecs_meta_dotmember(&cur, token) != 0) { + goto error; + } + } else { + if (ecs_meta_set_string(&cur, token) != 0) { + goto error; + } + } + } else if (token_kind == JsonNumber) { + double number = atof(token); + if (ecs_meta_set_float(&cur, number) != 0) { + goto error; + } + } else if (token_kind == JsonLargeInt) { + int64_t number = flecs_ito(int64_t, atoll(token)); + if (ecs_meta_set_int(&cur, number) != 0) { + goto error; + } + } else if (token_kind == JsonNull) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonTrue) { + if (ecs_meta_set_bool(&cur, true) != 0) { + goto error; + } + } else if (token_kind == JsonFalse) { + if (ecs_meta_set_bool(&cur, false) != 0) { + goto error; + } + } else { + goto error; + } + + if (token != token_buffer) { + ecs_os_free(token); + token = token_buffer; + } + + if (!depth) { + break; + } + } + + return json; +error: + return NULL; +} + +const char* ecs_entity_from_json( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc_param) +{ + ecs_json_token_t token_kind = 0; + char token[ECS_MAX_TOKEN_SIZE]; + + ecs_from_json_desc_t desc = {0}; + + const char *name = NULL, *expr = json, *ids = NULL, *values = NULL, *lah; + if (desc_param) { + desc = *desc_param; + } + + json = flecs_json_expect(json, JsonObjectOpen, token, &desc); + if (!json) { + goto error; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (!lah) { + goto error; + } + + if (token_kind == JsonObjectClose) { + return lah; + } + + json = flecs_json_expect_member(json, token, &desc); + if (!json) { + return NULL; + } + + if (!ecs_os_strcmp(token, "path")) { + json = flecs_json_expect(json, JsonString, token, &desc); + if (!json) { + goto error; + } + + ecs_add_fullpath(world, e, token); + + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } + + if (token_kind == JsonObjectClose) { + return json; + } else if (token_kind != JsonComma) { + ecs_parser_error(name, expr, json - expr, "unexpected character"); + goto error; + } + + json = flecs_json_expect_member_name(json, token, "ids", &desc); + if (!json) { + goto error; + } + } else if (ecs_os_strcmp(token, "ids")) { + ecs_parser_error(name, expr, json - expr, "expected member 'ids'"); + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; + } + + ids = json; + + json = flecs_json_skip_array(json, token, &desc); + if (!json) { + return NULL; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonObjectClose) { + if (token_kind != JsonComma) { + ecs_parser_error(name, expr, json - expr, "expected ','"); + goto error; + } + + json = flecs_json_expect_member_name(json, token, "values", &desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; + } + + values = json; + } + + do { + ecs_entity_t first = 0, second = 0, type_id = 0; + ecs_id_t id; + + ids = flecs_json_parse(ids, &token_kind, token); + if (!ids) { + goto error; + } + + if (token_kind == JsonArrayClose) { + if (values) { + if (values[0] != ']') { + ecs_parser_error(name, expr, values - expr, "expected ']'"); + goto error; + } + json = ecs_parse_ws_eol(values + 1); + } else { + json = ids; + } + + break; + } else if (token_kind == JsonArrayOpen) { + ids = flecs_json_parse_path(world, ids, token, &first, &desc); + if (!ids) { + goto error; + } + + ids = flecs_json_parse(ids, &token_kind, token); + if (!ids) { + goto error; + } + + if (token_kind == JsonComma) { + /* Id is a pair*/ + ids = flecs_json_parse_path(world, ids, token, &second, &desc); + if (!ids) { + goto error; + } + + ids = flecs_json_expect(ids, JsonArrayClose, token, &desc); + if (!ids) { + goto error; + } + } else if (token_kind != JsonArrayClose) { + ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); + goto error; + } + + lah = flecs_json_parse(ids, &token_kind, token); + if (!lah) { + goto error; + } + + if (token_kind == JsonComma) { + ids = lah; + } else if (token_kind != JsonArrayClose) { + ecs_parser_error(name, expr, lah - expr, "expected ',' or ']'"); + goto error; + } + } else { + ecs_parser_error(name, expr, lah - expr, "expected '[' or ']'"); + goto error; + } + + if (second) { + id = ecs_pair(first, second); + type_id = ecs_get_typeid(world, id); + if (!type_id) { + ecs_parser_error(name, expr, ids - expr, "id is not a type"); + goto error; + } + } else { + id = first; + type_id = first; + } + + /* Get mutable pointer */ + void *comp_ptr = ecs_get_mut_id(world, e, id); + if (!comp_ptr) { + char *idstr = ecs_id_str(world, id); + ecs_parser_error(name, expr, json - expr, + "id '%s' is not a valid component", idstr); + ecs_os_free(idstr); + goto error; + } + + if (values) { + ecs_from_json_desc_t parse_desc = { + .name = name, + .expr = expr, + }; + + values = ecs_ptr_from_json( + world, type_id, comp_ptr, values, &parse_desc); + if (!values) { + goto error; + } + + lah = flecs_json_parse(values, &token_kind, token); + if (!lah) { + goto error; + } + + if (token_kind == JsonComma) { + values = lah; + } else if (token_kind != JsonArrayClose) { + ecs_parser_error(name, expr, json - expr, + "expected ',' or ']'"); + goto error; + } else { + values = ecs_parse_ws_eol(values); + } + + ecs_modified_id(world, e, id); + } + } while(ids[0]); + + return flecs_json_expect(json, JsonObjectClose, token, &desc); +error: + return NULL; +} + +static +ecs_entity_t flecs_json_new_id( + ecs_world_t *world, + ecs_entity_t ser_id) +{ + /* Try to honor low id requirements */ + if (ser_id < FLECS_HI_COMPONENT_ID) { + return ecs_new_low_id(world); + } else { + return ecs_new_id(world); + } +} + +static +ecs_entity_t flecs_json_lookup( + ecs_world_t *world, + ecs_entity_t parent, + const char *name, + const ecs_from_json_desc_t *desc) +{ + ecs_entity_t scope = 0; + if (parent) { + scope = ecs_set_scope(world, parent); + } + ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); + if (parent) { + ecs_set_scope(world, scope); + } + return result; +} + +static +void flecs_json_mark_reserved( + ecs_map_t *anonymous_ids, + ecs_entity_t e) +{ + ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); + ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); + reserved[0] = 0; +} + +static +bool flecs_json_name_is_anonymous( + const char *name) +{ + if (isdigit(name[0])) { + const char *ptr; + for (ptr = name + 1; *ptr; ptr ++) { + if (!isdigit(*ptr)) { + break; + } + } + if (!(*ptr)) { + return true; + } + } + return false; +} + +static +ecs_entity_t flecs_json_ensure_entity( + ecs_world_t *world, + const char *name, + ecs_map_t *anonymous_ids) +{ + ecs_entity_t e = 0; + + if (flecs_json_name_is_anonymous(name)) { + /* Anonymous entity, find or create mapping to new id */ + ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(name)); + ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); + if (deser_id) { + if (!deser_id[0]) { + /* Id is already issued by deserializer, create new id */ + deser_id[0] = flecs_json_new_id(world, ser_id); + + /* Mark new id as reserved */ + flecs_json_mark_reserved(anonymous_ids, deser_id[0]); + } else { + /* Id mapping exists */ + } + } else { + /* Id has not yet been issued by deserializer, which means it's safe + * to use. This allows the deserializer to bind to existing + * anonymous ids, as they will never be reissued. */ + deser_id = ecs_map_ensure(anonymous_ids, ser_id); + if (!ecs_exists(world, ser_id) || ecs_is_alive(world, ser_id)) { + /* Only use existing id if it's alive or doesn't exist yet. The + * id could have been recycled for another entity */ + deser_id[0] = ser_id; + ecs_ensure(world, ser_id); + } else { + /* If id exists and is not alive, create a new id */ + deser_id[0] = flecs_json_new_id(world, ser_id); + + /* Mark new id as reserved */ + flecs_json_mark_reserved(anonymous_ids, deser_id[0]); + } + } + + e = deser_id[0]; + } else { + e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); + if (!e) { + e = ecs_entity(world, { .name = name }); + flecs_json_mark_reserved(anonymous_ids, e); + } + } + + return e; +} + +static +ecs_table_t* flecs_json_parse_table( + ecs_world_t *world, + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + ecs_table_t *table = NULL; + + do { + ecs_id_t id = 0; + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonString, token, desc); + if (!json) { + goto error; + } + + ecs_entity_t first = flecs_json_lookup(world, 0, token, desc); + if (!first) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + json = flecs_json_expect(json, JsonString, token, desc); + if (!json) { + goto error; + } + + ecs_entity_t second = flecs_json_lookup(world, 0, token, desc); + if (!second) { + goto error; + } + + id = ecs_pair(first, second); + + json = flecs_json_expect(json, JsonArrayClose, token, desc); + if (!json) { + goto error; + } + } else if (token_kind == JsonArrayClose) { + id = first; + } else { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']"); + goto error; + } + + table = ecs_table_add_id(world, table, id); + if (!table) { + goto error; + } + + const char *lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + json = lah; + } else if (token_kind == JsonArrayClose) { + break; + } else { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + goto error; + } + } while (json[0]); + + return table; +error: + return NULL; +} + +static +int flecs_json_parse_entities( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_table_t *table, + ecs_entity_t parent, + const char *json, + char *token, + ecs_vec_t *records, + const ecs_from_json_desc_t *desc) +{ + char name_token[ECS_MAX_TOKEN_SIZE]; + ecs_json_token_t token_kind = 0; + ecs_vec_clear(records); + + do { + json = flecs_json_parse(json, &token_kind, name_token); + if (!json) { + goto error; + } + if ((token_kind != JsonNumber) && (token_kind != JsonString)) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected number or string"); + goto error; + } + + ecs_entity_t e = flecs_json_lookup(world, parent, name_token, desc); + ecs_record_t *r = flecs_entities_try(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + if (r->table != table) { + bool cleared = false; + if (r->table) { + ecs_commit(world, e, r, r->table, NULL, &r->table->type); + cleared = true; + } + ecs_commit(world, e, r, table, &table->type, NULL); + if (cleared) { + char *entity_name = strrchr(name_token, '.'); + if (entity_name) { + entity_name ++; + } else { + entity_name = name_token; + } + if (!flecs_json_name_is_anonymous(entity_name)) { + ecs_set_name(world, e, entity_name); + } + } + } + + ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); + ecs_record_t** elem = ecs_vec_append_t(a, records, ecs_record_t*); + *elem = r; + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + goto error; + } + } while(json[0]); + + return 0; +error: + return -1; +} + +static +const char* flecs_json_parse_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t index, + const char *json, + char *token, + ecs_vec_t *records, + const ecs_from_json_desc_t *desc) +{ + if (!table->column_count) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "table has no components"); + goto error; + } + + if (index >= table->type.count) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "more value arrays than component columns in table"); + goto error; + } + + int32_t data_column = table->column_map[index]; + if (data_column == -1) { + char *table_str = ecs_table_str(world, table); + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "values provided for tag at column %d of table [%s]", + index, table_str); + + ecs_os_free(table_str); + goto error; + } + + ecs_json_token_t token_kind = 0; + ecs_column_t *column = &table->data.columns[data_column]; + ecs_type_info_t *ti = column->ti; + ecs_size_t size = ti->size; + ecs_entity_t type = ti->component; + ecs_record_t **record_array = ecs_vec_first_t(records, ecs_record_t*); + int32_t entity = 0; + + do { + ecs_record_t *r = record_array[entity]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + + void *ptr = ecs_vec_get(&column->data, size, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + json = ecs_ptr_from_json(world, type, ptr, json, desc); + if (!json) { + break; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + } + + entity ++; + } while (json[0]); + + return json; +error: + return NULL; +} + +static +const char* flecs_json_parse_values( + ecs_world_t *world, + ecs_table_t *table, + const char *json, + char *token, + ecs_vec_t *records, + ecs_vec_t *columns_set, + const ecs_from_json_desc_t *desc) +{ + ecs_allocator_t *a = &world->allocator; + ecs_json_token_t token_kind = 0; + int32_t column = 0; + + ecs_vec_clear(columns_set); + + do { + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } + + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind == JsonArrayOpen) { + json = flecs_json_parse_column(world, table, column, + json, token, records, desc); + if (!json) { + goto error; + } + + ecs_id_t *id_set = ecs_vec_append_t(a, columns_set, ecs_id_t); + *id_set = table->type.array[column]; + + column ++; + } else if (token_kind == JsonNumber) { + if (!ecs_os_strcmp(token, "0")) { + column ++; /* no data */ + } else { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "unexpected number"); + goto error; + } + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + goto error; + } + } while (json[0]); + + /* Send OnSet notifications */ + ecs_defer_begin(world); + ecs_type_t type = { + .array = columns_set->array, + .count = columns_set->count }; + + int32_t table_count = ecs_table_count(table); + int32_t i, record_count = ecs_vec_count(records); + + /* If the entire table was inserted, send bulk notification */ + if (table_count == ecs_vec_count(records)) { + flecs_notify_on_set(world, table, 0, ecs_table_count(table), &type, true); + } else { + ecs_record_t **rvec = ecs_vec_first_t(records, ecs_record_t*); + for (i = 0; i < record_count; i ++) { + ecs_record_t *r = rvec[i]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + flecs_notify_on_set(world, table, row, 1, &type, true); + } + } + + ecs_defer_end(world); + + return json; +error: + return NULL; +} + +static +const char* flecs_json_parse_result( + ecs_world_t *world, + ecs_allocator_t *a, + const char *json, + char *token, + ecs_vec_t *records, + ecs_vec_t *columns_set, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + const char *ids = NULL, *values = NULL, *entities = NULL; + + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member_name(json, token, "ids", desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + ids = json; /* store start of ids array */ + + json = flecs_json_skip_array(json, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonComma, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + + ecs_entity_t parent = 0; + if (!ecs_os_strcmp(token, "parent")) { + json = flecs_json_expect(json, JsonString, token, desc); + if (!json) { + goto error; + } + parent = ecs_lookup_fullpath(world, token); + + json = flecs_json_expect(json, JsonComma, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + } + + if (ecs_os_strcmp(token, "entities")) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected 'entities'"); + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + entities = json; /* store start of entity id array */ + + json = flecs_json_skip_array(json, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + json = flecs_json_expect_member_name(json, token, "values", desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + values = json; /* store start of entities array */ + } else if (token_kind != JsonObjectClose) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or '}'"); + goto error; + } + + /* Find table from ids */ + ecs_table_t *table = flecs_json_parse_table(world, ids, token, desc); + if (!table) { + goto error; + } + + /* Add entities to table */ + if (flecs_json_parse_entities(world, a, table, parent, + entities, token, records, desc)) + { + goto error; + } + + /* Parse values */ + if (values) { + json = flecs_json_parse_values(world, table, values, token, + records, columns_set, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonObjectClose, token, desc); + if (!json) { + goto error; + } + } + + return json; +error: + return NULL; +} + +const char* ecs_world_from_json( + ecs_world_t *world, + const char *json, + const ecs_from_json_desc_t *desc_arg) +{ + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; + + ecs_from_json_desc_t desc = {0}; + ecs_allocator_t *a = &world->allocator; + ecs_vec_t records; + ecs_vec_t columns_set; + ecs_map_t anonymous_ids; + ecs_vec_init_t(a, &records, ecs_record_t*, 0); + ecs_vec_init_t(a, &columns_set, ecs_id_t, 0); + ecs_map_init(&anonymous_ids, a); + + const char *name = NULL, *expr = json, *lah; + if (desc_arg) { + desc = *desc_arg; + } + + if (!desc.lookup_action) { + desc.lookup_action = (ecs_entity_t(*)( + const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; + desc.lookup_ctx = &anonymous_ids; + } + + json = flecs_json_expect(json, JsonObjectOpen, token, &desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member_name(json, token, "results", &desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + json = lah; + goto end; + } + + do { + json = flecs_json_parse_result(world, a, json, token, + &records, &columns_set, &desc); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(name, expr, json - expr, + "expected ',' or ']'"); + goto error; + } + } while(json && json[0]); + +end: + ecs_vec_fini_t(a, &records, ecs_record_t*); + ecs_vec_fini_t(a, &columns_set, ecs_id_t); + ecs_map_fini(&anonymous_ids); + + json = flecs_json_expect(json, JsonObjectClose, token, &desc); + if (!json) { + goto error; + } + + return json; +error: + ecs_vec_fini_t(a, &records, ecs_record_t*); + ecs_vec_fini_t(a, &columns_set, ecs_id_t); + ecs_map_fini(&anonymous_ids); + + return NULL; +} + +#endif + +/** + * @file json/json.c + * @brief JSON serializer utilities. + */ + +#include + +#ifdef FLECS_JSON + +static +const char* flecs_json_token_str( + ecs_json_token_t token_kind) +{ + switch(token_kind) { + case JsonObjectOpen: return "{"; + case JsonObjectClose: return "}"; + case JsonArrayOpen: return "["; + case JsonArrayClose: return "]"; + case JsonColon: return ":"; + case JsonComma: return ","; + case JsonNumber: return "number"; + case JsonLargeInt: return "large integer"; + case JsonLargeString: + case JsonString: return "string"; + case JsonTrue: return "true"; + case JsonFalse: return "false"; + case JsonNull: return "null"; + case JsonInvalid: return "invalid"; + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return "<>"; +} + +const char* flecs_json_parse( + const char *json, + ecs_json_token_t *token_kind, + char *token) +{ + json = ecs_parse_ws_eol(json); + + char ch = json[0]; + + if (ch == '{') { + token_kind[0] = JsonObjectOpen; + return json + 1; + } else if (ch == '}') { + token_kind[0] = JsonObjectClose; + return json + 1; + } else if (ch == '[') { + token_kind[0] = JsonArrayOpen; + return json + 1; + } else if (ch == ']') { + token_kind[0] = JsonArrayClose; + return json + 1; + } else if (ch == ':') { + token_kind[0] = JsonColon; + return json + 1; + } else if (ch == ',') { + token_kind[0] = JsonComma; + return json + 1; + } else if (ch == '"') { + const char *start = json; + char *token_ptr = token; + json ++; + for (; (ch = json[0]); ) { + if (ch == '"') { + json ++; + token_ptr[0] = '\0'; + break; + } + + if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) { + /* Token doesn't fit in buffer, signal to app to try again with + * dynamic buffer. */ + token_kind[0] = JsonLargeString; + return start; + } + + json = ecs_chrparse(json, token_ptr ++); + } + + if (!ch) { + token_kind[0] = JsonInvalid; + return NULL; + } else { + token_kind[0] = JsonString; + return json; + } + } else if (isdigit(ch) || (ch == '-')) { + token_kind[0] = JsonNumber; + const char *result = ecs_parse_digit(json, token); + + /* Cheap initial check if parsed token could represent large int */ + if (result - json > 15) { + /* Less cheap secondary check to see if number is integer */ + if (!strchr(token, '.')) { + token_kind[0] = JsonLargeInt; + } + } + + return result; + } else if (isalpha(ch)) { + if (!ecs_os_strncmp(json, "null", 4)) { + token_kind[0] = JsonNull; + json += 4; + } else + if (!ecs_os_strncmp(json, "true", 4)) { + token_kind[0] = JsonTrue; + json += 4; + } else + if (!ecs_os_strncmp(json, "false", 5)) { + token_kind[0] = JsonFalse; + json += 5; + } + + if (isalpha(json[0]) || isdigit(json[0])) { + token_kind[0] = JsonInvalid; + return NULL; + } + + return json; + } else { + token_kind[0] = JsonInvalid; + return NULL; + } +} + +const char* flecs_json_parse_large_string( + const char *json, + ecs_strbuf_t *buf) +{ + if (json[0] != '"') { + return NULL; /* can only parse strings */ + } + + char ch, ch_out; + json ++; + for (; (ch = json[0]); ) { + if (ch == '"') { + json ++; + break; + } + + json = ecs_chrparse(json, &ch_out); + ecs_strbuf_appendch(buf, ch_out); + } + + if (!ch) { + return NULL; + } else { + return json; + } +} + +const char* flecs_json_expect( + const char *json, + ecs_json_token_t token_kind, + char *token, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t kind = 0; + json = flecs_json_parse(json, &kind, token); + if (kind == JsonInvalid) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid json"); + return NULL; + } else if (kind != token_kind) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s", + flecs_json_token_str(token_kind)); + return NULL; + } + return json; +} + +const char* flecs_json_expect_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + json = flecs_json_expect(json, JsonString, token, desc); + if (!json) { + return NULL; + } + json = flecs_json_expect(json, JsonColon, token, desc); + if (!json) { + return NULL; + } + return json; +} + +const char* flecs_json_expect_member_name( + const char *json, + char *token, + const char *member_name, + const ecs_from_json_desc_t *desc) +{ + json = flecs_json_expect_member(json, token, desc); + if (!json) { + return NULL; + } + if (ecs_os_strcmp(token, member_name)) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected member '%s'", member_name); + return NULL; + } + return json; +} + +const char* flecs_json_skip_object( + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + + while ((json = flecs_json_parse(json, &token_kind, token))) { + if (token_kind == JsonObjectOpen) { + json = flecs_json_skip_object(json, token, desc); + } else if (token_kind == JsonArrayOpen) { + json = flecs_json_skip_array(json, token, desc); + } else if (token_kind == JsonObjectClose) { + return json; + } else if (token_kind == JsonArrayClose) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected }"); + return NULL; + } + } + + ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }"); + return NULL; +} + +const char* flecs_json_skip_array( + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + + while ((json = flecs_json_parse(json, &token_kind, token))) { + if (token_kind == JsonObjectOpen) { + json = flecs_json_skip_object(json, token, desc); + } else if (token_kind == JsonArrayOpen) { + json = flecs_json_skip_array(json, token, desc); + } else if (token_kind == JsonObjectClose) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ]"); + return NULL; + } else if (token_kind == JsonArrayClose) { + return json; + } + } + + ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); + return NULL; +} + +void flecs_json_next( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_next(buf); +} + +void flecs_json_number( + ecs_strbuf_t *buf, + double value) +{ + ecs_strbuf_appendflt(buf, value, '"'); +} + +void flecs_json_true( + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendlit(buf, "true"); +} + +void flecs_json_false( + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendlit(buf, "false"); +} + +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value) +{ + if (value) { + flecs_json_true(buf); + } else { + flecs_json_false(buf); + } +} + +void flecs_json_array_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "[", ", "); +} + +void flecs_json_array_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "]"); +} + +void flecs_json_object_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "{", ", "); +} + +void flecs_json_object_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "}"); +} + +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, value); + ecs_strbuf_appendch(buf, '"'); +} + +void flecs_json_string_escape( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_size_t length = ecs_stresc(NULL, 0, '"', value); + if (length == ecs_os_strlen(value)) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstrn(buf, value, length); + ecs_strbuf_appendch(buf, '"'); + } else { + char *out = ecs_os_malloc(length + 3); + ecs_stresc(out + 1, length, '"', value); + out[0] = '"'; + out[length + 1] = '"'; + out[length + 2] = '\0'; + ecs_strbuf_appendstr(buf, out); + ecs_os_free(out); + } +} + +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name) +{ + flecs_json_membern(buf, name, ecs_os_strlen(name)); +} + +void flecs_json_membern( + ecs_strbuf_t *buf, + const char *name, + int32_t name_len) +{ + ecs_strbuf_list_appendch(buf, '"'); + ecs_strbuf_appendstrn(buf, name, name_len); + ecs_strbuf_appendlit(buf, "\":"); +} + +void flecs_json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); + ecs_strbuf_appendch(buf, '"'); +} + +static +const char* flecs_json_entity_label( + const ecs_world_t *world, + ecs_entity_t e) +{ + const char *lbl = NULL; + if (!e) { + return "0"; + } +#ifdef FLECS_DOC + lbl = ecs_doc_get_name(world, e); +#else + lbl = ecs_get_name(world, e); +#endif + return lbl; +} + +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + const char *lbl = flecs_json_entity_label(world, e); + if (lbl) { + flecs_json_string_escape(buf, lbl); + } else { + ecs_strbuf_appendch(buf, '0'); + } +} + +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + (void)world; + (void)e; + + const char *color = NULL; +#ifdef FLECS_DOC + color = ecs_doc_get_color(world, e); +#endif + + if (color) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, color); + ecs_strbuf_appendch(buf, '"'); + } else { + ecs_strbuf_appendch(buf, '0'); + } +} + +void flecs_json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_strbuf_appendch(buf, '['); + + if (ECS_IS_PAIR(id)) { + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_second(world, id); + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendch(buf, ','); + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_strbuf_appendch(buf, '"'); + } else { + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + ecs_strbuf_appendch(buf, '"'); + } + + ecs_strbuf_appendch(buf, ']'); +} + +void flecs_json_id_member( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + ecs_strbuf_appendch(buf, '('); + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_second(world, id); + { + const char *lbl = flecs_json_entity_label(world, first); + if (lbl) { + ecs_strbuf_appendstr(buf, lbl); + } + } + ecs_strbuf_appendch(buf, ','); + { + const char *lbl = flecs_json_entity_label(world, second); + if (lbl) { + ecs_strbuf_appendstr(buf, lbl); + } + } + ecs_strbuf_appendch(buf, ')'); + } else { + const char *lbl = flecs_json_entity_label(world, id & ECS_COMPONENT_MASK); + if (lbl) { + ecs_strbuf_appendstr(buf, lbl); + } + } +} + +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind) +{ + return kind - EcsOpPrimitive; +} + +#endif + +/** + * @file json/serialize.c + * @brief Serialize (component) values to JSON strings. + */ + + +#ifdef FLECS_JSON + +static +int json_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str, + int32_t in_array); + +static +int json_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str); + +/* Serialize enumeration */ +static +int json_ser_enum( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); + ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t value = *(const int32_t*)base; + + /* Enumeration constants are stored in a map that is keyed on the + * enumeration value. */ + ecs_enum_constant_t *constant = ecs_map_get_deref(&enum_type->constants, + ecs_enum_constant_t, (ecs_map_key_t)value); + if (!constant) { + /* If the value is not found, it is not a valid enumeration constant */ + char *name = ecs_get_fullpath(world, op->type); + ecs_err("enumeration value '%d' of type '%s' is not a valid constant", + value, name); + ecs_os_free(name); + goto error; + } + + ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); + ecs_strbuf_appendch(str, '"'); + + return 0; +error: + return -1; +} + +/* Serialize bitmask */ +static +int json_ser_bitmask( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); + ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); + + uint32_t value = *(const uint32_t*)ptr; + if (!value) { + ecs_strbuf_appendch(str, '0'); + return 0; + } + + ecs_strbuf_list_push(str, "\"", "|"); + + /* Multiple flags can be set at a given time. Iterate through all the flags + * and append the ones that are set. */ + ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *constant = ecs_map_ptr(&it); + ecs_map_key_t key = ecs_map_key(&it); + if ((value & key) == key) { + ecs_strbuf_list_appendstr(str, + ecs_get_name(world, constant->constant)); + value -= (uint32_t)key; + } + } + + if (value != 0) { + /* All bits must have been matched by a constant */ + char *name = ecs_get_fullpath(world, op->type); + ecs_err("bitmask value '%u' of type '%s' contains invalid/unknown bits", + value, name); + ecs_os_free(name); + goto error; + } + + ecs_strbuf_list_pop(str, "\""); + + return 0; +error: + return -1; +} + +/* Serialize elements of a contiguous array */ +static +int json_ser_elements( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + int32_t elem_count, + int32_t elem_size, + ecs_strbuf_t *str, + bool is_array) +{ + flecs_json_array_push(str); + + const void *ptr = base; + + int i; + for (i = 0; i < elem_count; i ++) { + ecs_strbuf_list_next(str); + if (json_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { + return -1; + } + ptr = ECS_OFFSET(ptr, elem_size); + } + + flecs_json_array_pop(str); + + return 0; +} + +static +int json_ser_type_elements( + const ecs_world_t *world, + ecs_entity_t type, + const void *base, + int32_t elem_count, + ecs_strbuf_t *str, + bool is_array) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); + int32_t op_count = ecs_vec_count(&ser->ops); + + return json_ser_elements( + world, ops, op_count, base, elem_count, comp->size, str, is_array); +} + +/* Serialize array */ +static +int json_ser_array( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + const EcsArray *a = ecs_get(world, op->type, EcsArray); + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + + return json_ser_type_elements( + world, a->type, ptr, a->count, str, true); +} + +/* Serialize vector */ +static +int json_ser_vector( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + const ecs_vec_t *value = base; + const EcsVector *v = ecs_get(world, op->type, EcsVector); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t count = ecs_vec_count(value); + void *array = ecs_vec_first(value); + + /* Serialize contiguous buffer of vector */ + return json_ser_type_elements(world, v->type, array, count, str, false); +} + +typedef struct json_serializer_ctx_t { + ecs_strbuf_t *str; + bool is_collection; + bool is_struct; +} json_serializer_ctx_t; + +static +int json_ser_custom_value( + const ecs_serializer_t *ser, + ecs_entity_t type, + const void *value) +{ + json_serializer_ctx_t *json_ser = ser->ctx; + if (json_ser->is_collection) { + ecs_strbuf_list_next(json_ser->str); + } + return ecs_ptr_to_json_buf(ser->world, type, value, json_ser->str); +} + +static +int json_ser_custom_member( + const ecs_serializer_t *ser, + const char *name) +{ + json_serializer_ctx_t *json_ser = ser->ctx; + if (!json_ser->is_struct) { + ecs_err("serializer::member can only be called for structs"); + return -1; + } + flecs_json_member(json_ser->str, name); + return 0; +} + +static +int json_ser_custom_type( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); + ecs_assert(ct != NULL, ECS_INVALID_OPERATION, NULL); + ecs_assert(ct->as_type != 0, ECS_INVALID_OPERATION, NULL); + ecs_assert(ct->serialize != NULL, ECS_INVALID_OPERATION, + ecs_get_name(world, op->type)); + + const EcsMetaType *pt = ecs_get(world, ct->as_type, EcsMetaType); + ecs_assert(pt != NULL, ECS_INVALID_OPERATION, NULL); + + ecs_type_kind_t kind = pt->kind; + bool is_collection = false; + bool is_struct = false; + + if (kind == EcsStructType) { + flecs_json_object_push(str); + is_struct = true; + } else if (kind == EcsArrayType || kind == EcsVectorType) { + flecs_json_array_push(str); + is_collection = true; + } + + json_serializer_ctx_t json_ser = { + .str = str, + .is_struct = is_struct, + .is_collection = is_collection + }; + + ecs_serializer_t ser = { + .world = world, + .value = json_ser_custom_value, + .member = json_ser_custom_member, + .ctx = &json_ser + }; + + if (ct->serialize(&ser, base)) { + return -1; + } + + if (kind == EcsStructType) { + flecs_json_object_pop(str); + } else if (kind == EcsArrayType || kind == EcsVectorType) { + flecs_json_array_pop(str); + } + + return 0; +} + +/* Forward serialization to the different type kinds */ +static +int json_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + void *vptr = ECS_OFFSET(ptr, op->offset); + bool large_int = false; + if (op->kind == EcsOpI64) { + if (*(int64_t*)vptr >= 2147483648) { + large_int = true; + } + } else if (op->kind == EcsOpU64) { + if (*(uint64_t*)vptr >= 2147483648) { + large_int = true; + } + } + + if (large_int) { + ecs_strbuf_appendch(str, '"'); + } + + switch(op->kind) { + case EcsOpPush: + case EcsOpPop: + /* Should not be parsed as single op */ + ecs_throw(ECS_INVALID_PARAMETER, NULL); + break; + case EcsOpF32: + ecs_strbuf_appendflt(str, + (ecs_f64_t)*(const ecs_f32_t*)vptr, '"'); + break; + case EcsOpF64: + ecs_strbuf_appendflt(str, + *(ecs_f64_t*)vptr, '"'); + break; + case EcsOpEnum: + if (json_ser_enum(world, op, vptr, str)) { + goto error; + } + break; + case EcsOpBitmask: + if (json_ser_bitmask(world, op, vptr, str)) { + goto error; + } + break; + case EcsOpArray: + if (json_ser_array(world, op, vptr, str)) { + goto error; + } + break; + case EcsOpVector: + if (json_ser_vector(world, op, vptr, str)) { + goto error; + } + break; + case EcsOpOpaque: + if (json_ser_custom_type(world, op, vptr, str)) { + goto error; + } + break; + case EcsOpEntity: { + ecs_entity_t e = *(const ecs_entity_t*)vptr; + if (!e) { + ecs_strbuf_appendch(str, '0'); + } else { + flecs_json_path(str, world, e); + } + break; + } + case EcsOpId: { + ecs_id_t id = *(const ecs_id_t*)vptr; + if (!id) { + ecs_strbuf_appendch(str, '0'); + } else { + flecs_json_id(str, world, id); + } + break; + } + + case EcsOpU64: + case EcsOpI64: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + if (ecs_primitive_to_expr_buf(world, + flecs_json_op_to_primitive_kind(op->kind), + ECS_OFFSET(ptr, op->offset), str)) + { + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + break; + + case EcsOpPrimitive: + case EcsOpScope: + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + + if (large_int) { + ecs_strbuf_appendch(str, '"'); + } + + return 0; +error: + return -1; +} + +/* Iterate over a slice of the type ops array */ +static +int json_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str, + int32_t in_array) +{ + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; + + if (in_array <= 0) { + if (op->name) { + flecs_json_member(str, op->name); + } + + int32_t elem_count = op->count; + if (elem_count > 1) { + /* Serialize inline array */ + if (json_ser_elements(world, op, op->op_count, base, + elem_count, op->size, str, true)) + { + return -1; + } + + i += op->op_count - 1; + continue; + } + } + + switch(op->kind) { + case EcsOpPush: + flecs_json_object_push(str); + in_array --; + break; + case EcsOpPop: + flecs_json_object_pop(str); + in_array ++; + break; + case EcsOpArray: + case EcsOpVector: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + case EcsOpString: + case EcsOpOpaque: + if (json_ser_type_op(world, op, base, str)) { + goto error; + } + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + } + + return 0; +error: + return -1; +} + +/* Iterate over the type ops of a type */ +int json_ser_type( + const ecs_world_t *world, + const ecs_vec_t *v_ops, + const void *base, + ecs_strbuf_t *str) +{ + ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); + int32_t count = ecs_vec_count(v_ops); + return json_ser_type_ops(world, ops, count, base, str, 0); +} + +static +int array_to_json_buf_w_type_data( + const ecs_world_t *world, + const void *ptr, + int32_t count, + ecs_strbuf_t *buf, + const EcsComponent *comp, + const EcsMetaTypeSerialized *ser) +{ + if (count) { + ecs_size_t size = comp->size; + + flecs_json_array_push(buf); + + do { + ecs_strbuf_list_next(buf); + if (json_ser_type(world, &ser->ops, ptr, buf)) { + return -1; + } + + ptr = ECS_OFFSET(ptr, size); + } while (-- count); + + flecs_json_array_pop(buf); + } else { + if (json_ser_type(world, &ser->ops, ptr, buf)) { + return -1; + } + } + + return 0; +} + +int ecs_array_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + int32_t count, + ecs_strbuf_t *buf) +{ + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize to JSON, '%s' is not a component", path); + ecs_os_free(path); + return -1; + } + + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + char *path = ecs_get_fullpath(world, type); + ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); + ecs_os_free(path); + return -1; + } + + return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); +} + +char* ecs_array_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr, + int32_t count) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +int ecs_ptr_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf) +{ + return ecs_array_to_json_buf(world, type, ptr, 0, buf); +} + +char* ecs_ptr_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + return ecs_array_to_json(world, type, ptr, 0); +} + +static +bool flecs_json_skip_id( + const ecs_world_t *world, + ecs_id_t id, + const ecs_entity_to_json_desc_t *desc, + ecs_entity_t ent, + ecs_entity_t inst, + ecs_entity_t *pred_out, + ecs_entity_t *obj_out, + ecs_entity_t *role_out, + bool *hidden_out) +{ + bool is_base = ent != inst; + ecs_entity_t pred = 0, obj = 0, role = 0; + bool hidden = false; + + if (ECS_HAS_ID_FLAG(id, PAIR)) { + pred = ecs_pair_first(world, id); + obj = ecs_pair_second(world, id); + } else { + pred = id & ECS_COMPONENT_MASK; + if (id & ECS_ID_FLAGS_MASK) { + role = id & ECS_ID_FLAGS_MASK; + } + } + + if (is_base) { + if (ecs_has_id(world, pred, EcsDontInherit)) { + return true; + } + } + if (!desc || !desc->serialize_private) { + if (ecs_has_id(world, pred, EcsPrivate)) { + return true; + } + } + if (is_base) { + if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { + hidden = true; + } + } + if (hidden && (!desc || !desc->serialize_hidden)) { + return true; + } + + *pred_out = pred; + *obj_out = obj; + *role_out = role; + if (hidden_out) *hidden_out = hidden; + + return false; +} + +static +int flecs_json_append_type_labels( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + (void)world; (void)buf; (void)ids; (void)count; (void)ent; (void)inst; + (void)desc; + +#ifdef FLECS_DOC + if (!desc || !desc->serialize_id_labels) { + return 0; + } + + flecs_json_memberl(buf, "id_labels"); + flecs_json_array_push(buf); + + int32_t i; + for (i = 0; i < count; i ++) { + ecs_entity_t pred = 0, obj = 0, role = 0; + if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { + continue; + } + + if (obj && (pred == EcsUnion)) { + pred = obj; + obj = ecs_get_target(world, ent, pred, 0); + if (!ecs_is_alive(world, obj)) { + /* Union relationships aren't automatically cleaned up, so they + * can contain invalid entity ids. Don't serialize value until + * relationship is valid again. */ + continue; + } + } + + if (desc && desc->serialize_id_labels) { + flecs_json_next(buf); + + flecs_json_array_push(buf); + flecs_json_next(buf); + flecs_json_label(buf, world, pred); + if (obj) { + flecs_json_next(buf); + flecs_json_label(buf, world, obj); + } + + flecs_json_array_pop(buf); + } + } + + flecs_json_array_pop(buf); +#endif + return 0; +} + +static +int flecs_json_append_type_values( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + if (!desc || !desc->serialize_values) { + return 0; + } + + flecs_json_memberl(buf, "values"); + flecs_json_array_push(buf); + + int32_t i; + for (i = 0; i < count; i ++) { + bool hidden; + ecs_entity_t pred = 0, obj = 0, role = 0; + ecs_id_t id = ids[i]; + if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, + &hidden)) + { + continue; + } + + if (!hidden) { + bool serialized = false; + ecs_entity_t typeid = ecs_get_typeid(world, id); + if (typeid) { + const EcsMetaTypeSerialized *ser = ecs_get( + world, typeid, EcsMetaTypeSerialized); + if (ser) { + const void *ptr = ecs_get_id(world, ent, id); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_json_next(buf); + if (json_ser_type(world, &ser->ops, ptr, buf) != 0) { + /* Entity contains invalid value */ + return -1; + } + serialized = true; + } + } + if (!serialized) { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } + } else { + if (!desc || desc->serialize_hidden) { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +static +int flecs_json_append_type_info( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + if (!desc || !desc->serialize_type_info) { + return 0; + } + + flecs_json_memberl(buf, "type_info"); + flecs_json_array_push(buf); + + int32_t i; + for (i = 0; i < count; i ++) { + bool hidden; + ecs_entity_t pred = 0, obj = 0, role = 0; + ecs_id_t id = ids[i]; + if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, + &hidden)) + { + continue; + } + + if (!hidden) { + ecs_entity_t typeid = ecs_get_typeid(world, id); + if (typeid) { + flecs_json_next(buf); + if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { + return -1; + } + } else { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } + } else { + if (!desc || desc->serialize_hidden) { + flecs_json_next(buf); + flecs_json_number(buf, 0); + } + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +static +int flecs_json_append_type_hidden( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_id_t *ids, + int32_t count, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + if (!desc || !desc->serialize_hidden) { + return 0; + } + + if (ent == inst) { + return 0; /* if this is not a base, components are never hidden */ + } + + flecs_json_memberl(buf, "hidden"); + flecs_json_array_push(buf); + + int32_t i; + for (i = 0; i < count; i ++) { + bool hidden; + ecs_entity_t pred = 0, obj = 0, role = 0; + ecs_id_t id = ids[i]; + if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, + &hidden)) + { + continue; + } + + flecs_json_next(buf); + flecs_json_bool(buf, hidden); + } + + flecs_json_array_pop(buf); + + return 0; +} + +static +int flecs_json_append_type( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + const ecs_id_t *ids = NULL; + int32_t i, count = 0; + + const ecs_type_t *type = ecs_get_type(world, ent); + if (type) { + ids = type->array; + count = type->count; + } + + if (!desc || desc->serialize_ids) { + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); + + for (i = 0; i < count; i ++) { + ecs_entity_t pred = 0, obj = 0, role = 0; + if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { + continue; + } + + if (obj && (pred == EcsUnion)) { + pred = obj; + obj = ecs_get_target(world, ent, pred, 0); + if (!ecs_is_alive(world, obj)) { + /* Union relationships aren't automatically cleaned up, so they + * can contain invalid entity ids. Don't serialize value until + * relationship is valid again. */ + continue; + } + } + + flecs_json_next(buf); + flecs_json_array_push(buf); + flecs_json_next(buf); + flecs_json_path(buf, world, pred); + if (obj || role) { + flecs_json_next(buf); + if (obj) { + flecs_json_path(buf, world, obj); + } else { + flecs_json_number(buf, 0); + } + if (role) { + flecs_json_next(buf); + flecs_json_string(buf, ecs_id_flag_str(role)); + } + } + flecs_json_array_pop(buf); + } + flecs_json_array_pop(buf); + } + + if (flecs_json_append_type_labels(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + if (flecs_json_append_type_values(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + if (flecs_json_append_type_info(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + if (flecs_json_append_type_hidden(world, buf, ids, count, ent, inst, desc)) { + return -1; + } + + return 0; +} + +static +int flecs_json_append_base( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t ent, + ecs_entity_t inst, + const ecs_entity_to_json_desc_t *desc) +{ + const ecs_type_t *type = ecs_get_type(world, ent); + ecs_id_t *ids = NULL; + int32_t i, count = 0; + if (type) { + ids = type->array; + count = type->count; + } + + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_RELATION(id, EcsIsA)) { + if (flecs_json_append_base(world, buf, ecs_pair_second(world, id), inst, desc)) + { + return -1; + } + } + } + + ecs_strbuf_list_next(buf); + flecs_json_object_push(buf); + flecs_json_memberl(buf, "path"); + flecs_json_path(buf, world, ent); + + if (flecs_json_append_type(world, buf, ent, inst, desc)) { + return -1; + } + + flecs_json_object_pop(buf); + + return 0; +} + +#ifdef FLECS_ALERTS +static +int flecs_json_serialize_entity_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity, + const EcsAlertsActive *alerts, + bool self) +{ + ecs_assert(alerts != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_iter_t it = ecs_map_iter(&alerts->alerts); + while (ecs_map_next(&it)) { + flecs_json_next(buf); + flecs_json_object_push(buf); + ecs_entity_t ai = ecs_map_value(&it); + char *alert_name = ecs_get_fullpath(world, ai); + flecs_json_memberl(buf, "alert"); + flecs_json_string(buf, alert_name); + ecs_os_free(alert_name); + + ecs_entity_t severity_id = ecs_get_target( + world, ai, ecs_id(EcsAlert), 0); + const char *severity = ecs_get_name(world, severity_id); + + const EcsAlertInstance *alert = ecs_get( + world, ai, EcsAlertInstance); + if (alert) { + if (alert->message) { + flecs_json_memberl(buf, "message"); + flecs_json_string(buf, alert->message); + } + flecs_json_memberl(buf, "severity"); + flecs_json_string(buf, severity); + + if (!self) { + char *path = ecs_get_fullpath(world, entity); + flecs_json_memberl(buf, "path"); + flecs_json_string(buf, path); + ecs_os_free(path); + } + } + flecs_json_object_pop(buf); + } + + return 0; +} + +static +int flecs_json_serialize_children_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity) +{ + ecs_filter_t f = ECS_FILTER_INIT; + ecs_filter(ECS_CONST_CAST(ecs_world_t*, world), { + .storage = &f, + .terms = {{ .id = ecs_pair(EcsChildOf, entity) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + while (ecs_filter_next(&it)) { + EcsAlertsActive *alerts = ecs_table_get_id( + world, it.table, ecs_id(EcsAlertsActive), it.offset); + + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_entity_t child = it.entities[i]; + if (alerts) { + if (flecs_json_serialize_entity_alerts( + world, buf, child, &alerts[i], false)) + { + goto error; + } + } + + ecs_record_t *r = flecs_entities_get(world, it.entities[i]); + if (r->row & EcsEntityIsTraversable) { + if (flecs_json_serialize_children_alerts( + world, buf, child)) + { + goto error; + } + } + } + } + + ecs_filter_fini(&f); + + return 0; +error: + return -1; +} +#endif + +static +int flecs_json_serialize_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity) +{ + (void)world; + (void)buf; + (void)entity; + +#ifdef FLECS_ALERTS + if (!ecs_id(EcsAlertsActive)) { + return 0; /* Alert module not imported */ + } + + flecs_json_memberl(buf, "alerts"); + flecs_json_array_push(buf); + const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); + if (alerts) { + flecs_json_serialize_entity_alerts(world, buf, entity, alerts, true); + } + flecs_json_serialize_children_alerts(world, buf, entity); + flecs_json_array_pop(buf); +#endif + return 0; +} + +static +int flecs_json_serialize_refs_idr( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_id_record_t *idr) +{ + char *id_str = ecs_id_str(world, ecs_pair_first(world, idr->id)); + + flecs_json_member(buf, id_str); + ecs_os_free(id_str); + + flecs_json_array_push(buf); + + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + int32_t i, count = ecs_table_count(table); + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + flecs_json_next(buf); + flecs_json_path(buf, world, e); + } + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +static +int flecs_json_serialize_refs( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity, + ecs_entity_t relationship) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(relationship, entity)); + + if (idr) { + if (relationship == EcsWildcard) { + ecs_id_record_t *cur = idr; + while ((cur = cur->second.next)) { + flecs_json_serialize_refs_idr(world, buf, cur); + } + } else { + flecs_json_serialize_refs_idr(world, buf, idr); + } + } + + return 0; +} + +static +int flecs_json_serialize_matches( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair_t(EcsPoly, EcsQuery)); + + if (idr) { + ecs_table_cache_iter_t it; + if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); + + int32_t i, count = ecs_table_count(table); + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + for (i = 0; i < count; i ++) { + ecs_poly_t *q = queries[i].poly; + ecs_iter_t qit; + ecs_iter_poly(world, q, &qit, NULL); + if (!qit.variables) { + ecs_iter_fini(&qit); + continue; + } + ecs_iter_set_var(&qit, 0, entity); + if (ecs_iter_is_true(&qit)) { + flecs_json_next(buf); + flecs_json_path(buf, world, entities[i]); + } + } + } + } + } + + return 0; +} + +int ecs_entity_to_json_buf( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_strbuf_t *buf, + const ecs_entity_to_json_desc_t *desc) +{ + if (!entity || !ecs_is_valid(world, entity)) { + return -1; + } + + flecs_json_object_push(buf); + + if (!desc || desc->serialize_path) { + flecs_json_memberl(buf, "path"); + flecs_json_path(buf, world, entity); + } + +#ifdef FLECS_DOC + if (desc && desc->serialize_label) { + flecs_json_memberl(buf, "label"); + const char *doc_name = ecs_doc_get_name(world, entity); + if (doc_name) { + flecs_json_string_escape(buf, doc_name); + } else { + char num_buf[20]; + ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); + flecs_json_string(buf, num_buf); + } + } + + if (desc && desc->serialize_brief) { + const char *doc_brief = ecs_doc_get_brief(world, entity); + if (doc_brief) { + flecs_json_memberl(buf, "brief"); + flecs_json_string_escape(buf, doc_brief); + } + } + + if (desc && desc->serialize_link) { + const char *doc_link = ecs_doc_get_link(world, entity); + if (doc_link) { + flecs_json_memberl(buf, "link"); + flecs_json_string_escape(buf, doc_link); + } + } + + if (desc && desc->serialize_color) { + const char *doc_color = ecs_doc_get_color(world, entity); + if (doc_color) { + flecs_json_memberl(buf, "color"); + flecs_json_string_escape(buf, doc_color); + } + } +#endif + + const ecs_type_t *type = ecs_get_type(world, entity); + ecs_id_t *ids = NULL; + int32_t i, count = 0; + if (type) { + ids = type->array; + count = type->count; + } + + if (!desc || desc->serialize_base) { + if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { + flecs_json_memberl(buf, "is_a"); + flecs_json_array_push(buf); + + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + if (ECS_HAS_RELATION(id, EcsIsA)) { + if (flecs_json_append_base( + world, buf, ecs_pair_second(world, id), entity, desc)) + { + return -1; + } + } + } + + flecs_json_array_pop(buf); + } + } + + if (flecs_json_append_type(world, buf, entity, entity, desc)) { + goto error; + } + + if (desc && desc->serialize_alerts) { + if (flecs_json_serialize_alerts(world, buf, entity)) { + goto error; + } + } + + if (desc && desc->serialize_refs) { + flecs_json_memberl(buf, "refs"); + flecs_json_object_push(buf); + if (flecs_json_serialize_refs(world, buf, entity, desc->serialize_refs)) { + goto error; + } + flecs_json_object_pop(buf); + } + + if (desc && desc->serialize_matches) { + flecs_json_memberl(buf, "matches"); + flecs_json_array_push(buf); + if (flecs_json_serialize_matches(world, buf, entity)) { + goto error; + } + flecs_json_array_pop(buf); + } + + flecs_json_object_pop(buf); + + return 0; +error: + return -1; +} + +char* ecs_entity_to_json( + const ecs_world_t *world, + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +bool flecs_json_skip_variable( + const char *name) +{ + if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { + return true; + } else { + return false; + } +} + +static +void flecs_json_serialize_id( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) +{ + flecs_json_id(buf, world, id); +} + +static +void flecs_json_serialize_id_label( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) +{ + ecs_entity_t pred = id, obj = 0; + if (ECS_IS_PAIR(id)) { + pred = ecs_pair_first(world, id); + obj = ecs_pair_second(world, id); + } + + flecs_json_array_push(buf); + flecs_json_next(buf); + flecs_json_label(buf, world, pred); + if (obj) { + flecs_json_next(buf); + flecs_json_label(buf, world, obj); + } + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_iter_ids( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t field_count = it->field_count; + if (!field_count) { + return; + } + + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); + + for (int i = 0; i < field_count; i ++) { + flecs_json_next(buf); + flecs_json_serialize_id(world, it->terms[i].id, buf); + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_iter_id_labels( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t field_count = it->field_count; + if (!field_count) { + return; + } + + flecs_json_memberl(buf, "id_labels"); + flecs_json_array_push(buf); + + for (int i = 0; i < field_count; i ++) { + flecs_json_next(buf); + flecs_json_serialize_id_label(world, it->terms[i].id, buf); + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_id_str( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendch(buf, '"'); + if (ECS_IS_PAIR(id)) { + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_first(world, id); + ecs_strbuf_appendch(buf, '('); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_get_path_w_sep_buf( + world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + } + ecs_strbuf_appendch(buf, '"'); +} + +static +void flecs_json_serialize_type_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t field_count = it->field_count; + if (!field_count) { + return; + } + + if (it->flags & EcsIterNoData) { + return; + } + + flecs_json_memberl(buf, "type_info"); + flecs_json_object_push(buf); + + for (int i = 0; i < field_count; i ++) { + flecs_json_next(buf); + ecs_entity_t typeid = 0; + if (it->terms[i].inout != EcsInOutNone) { + typeid = ecs_get_typeid(world, it->terms[i].id); + } + if (typeid) { + flecs_json_serialize_id_str(world, typeid, buf); + ecs_strbuf_appendch(buf, ':'); + ecs_type_info_to_json_buf(world, typeid, buf); + } else { + flecs_json_serialize_id_str(world, it->terms[i].id, buf); + ecs_strbuf_appendlit(buf, ":0"); + } + } + + flecs_json_object_pop(buf); +} + +static +void flecs_json_serialize_field_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ctx) +{ + int32_t field_count = it->field_count; + if (!field_count || !it->query) { + return; + } + + const ecs_filter_t *q = it->query; + + flecs_json_memberl(buf, "field_info"); + flecs_json_array_push(buf); + + int f, t; + for (f = 0; f < field_count; f ++) { + flecs_json_next(buf); + flecs_json_object_push(buf); + flecs_json_memberl(buf, "id"); + + flecs_json_serialize_get_field_ctx(world, it, f, ctx); + ecs_json_value_ser_ctx_t *value_ctx = &ctx->value_ctx[f]; + + if (value_ctx->id_label) { + flecs_json_string(buf, value_ctx->id_label); + + const ecs_term_t *term = NULL; + for (t = 0; t < q->term_count; t ++) { + if (q->terms[t].field_index == f) { + term = &q->terms[t]; + break; + } + } + + ecs_assert(term != NULL, ECS_INTERNAL_ERROR, NULL); + + if (term->oper != EcsNot) { + if (term->oper == EcsOptional) { + flecs_json_memberl(buf, "optional"); + flecs_json_bool(buf, true); + } + + if (ECS_IS_PAIR(term->id)) { + if (term->first.flags & EcsIsEntity && term->first.id) { + if (ecs_has_id(world, term->first.id, EcsExclusive)) { + flecs_json_memberl(buf, "exclusive"); + flecs_json_bool(buf, true); + } + } + } + + if (value_ctx->type) { + flecs_json_memberl(buf, "type"); + flecs_json_label(buf, world, value_ctx->type); + } + + if (value_ctx->ser) { + flecs_json_memberl(buf, "schema"); + ecs_type_info_to_json_buf(world, value_ctx->type, buf); + } + } else { + flecs_json_memberl(buf, "not"); + flecs_json_bool(buf, true); + } + } else { + ecs_strbuf_appendlit(buf, "0"); + } + + flecs_json_object_pop(buf); + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { + char **variable_names = it->variable_names; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; + + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; + + if (!actual_count) { + flecs_json_memberl(buf, "vars"); + flecs_json_array_push(buf); + actual_count ++; + } + + ecs_strbuf_list_next(buf); + flecs_json_string(buf, var_name); + } + + if (actual_count) { + flecs_json_array_pop(buf); + } +} + +static +void flecs_json_serialize_iter_result_ids( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); + + for (int i = 0; i < it->field_count; i ++) { + flecs_json_next(buf); + flecs_json_serialize_id(world, ecs_field_id(it, i + 1), buf); + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_iter_result_id_labels( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + flecs_json_memberl(buf, "id_labels"); + flecs_json_array_push(buf); + + for (int i = 0; i < it->field_count; i ++) { + flecs_json_next(buf); + flecs_json_serialize_id_label(world, ecs_field_id(it, i + 1), buf); + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_iter_result_table_type( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + if (!it->table) { + return; + } + + if (desc->serialize_ids) { + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); + + ecs_type_t *type = &it->table->type; + for (int i = 0; i < type->count; i ++) { + ecs_id_t id = type->array[i]; + if (!desc->serialize_private) { + ecs_entity_t e = id; + if (ECS_IS_PAIR(id)) { + e = ecs_pair_first(world, id); + } + if (ecs_owns_id(world, e, EcsPrivate)) { + continue; + } + } + flecs_json_next(buf); + flecs_json_serialize_id(world, id, buf); + } + + flecs_json_array_pop(buf); + } + if (desc->serialize_id_labels) { + flecs_json_memberl(buf, "id_labels"); + flecs_json_array_push(buf); + + ecs_type_t *type = &it->table->type; + for (int i = 0; i < type->count; i ++) { + ecs_id_t id = type->array[i]; + if (!desc->serialize_private) { + ecs_entity_t e = id; + if (ECS_IS_PAIR(id)) { + e = ecs_pair_first(world, id); + } + if (ecs_owns_id(world, e, EcsPrivate)) { + continue; + } + } + flecs_json_next(buf); + flecs_json_serialize_id_label(world, id, buf); + } + + flecs_json_array_pop(buf); + } +} + +static +void flecs_json_serialize_iter_result_sources( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + flecs_json_memberl(buf, "sources"); + flecs_json_array_push(buf); + + for (int i = 0; i < it->field_count; i ++) { + flecs_json_next(buf); + ecs_entity_t subj = it->sources[i]; + if (subj) { + flecs_json_path(buf, world, subj); + } else { + ecs_strbuf_appendch(buf, '0'); + } + } + + flecs_json_array_pop(buf); +} + +bool flecs_json_serialize_iter_result_is_set( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + if (!(it->flags & EcsIterHasCondSet)) { + return false; + } + + flecs_json_memberl(buf, "is_set"); + flecs_json_array_push(buf); + + for (int i = 0; i < it->field_count; i ++) { + ecs_strbuf_list_next(buf); + if (ecs_field_is_set(it, i + 1)) { + flecs_json_true(buf); + } else { + flecs_json_false(buf); + } + } + + flecs_json_array_pop(buf); + + return true; +} + +static +void flecs_json_serialize_iter_result_variables( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + char **variable_names = it->variable_names; + ecs_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; + + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; + + if (!actual_count) { + flecs_json_memberl(buf, "vars"); + flecs_json_array_push(buf); + actual_count ++; + } + + ecs_strbuf_list_next(buf); + flecs_json_path(buf, world, variables[i].entity); + } + + if (actual_count) { + flecs_json_array_pop(buf); + } +} + +static +void flecs_json_serialize_iter_result_variable_labels( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + char **variable_names = it->variable_names; + ecs_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; + + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; + + if (!actual_count) { + flecs_json_memberl(buf, "var_labels"); + flecs_json_array_push(buf); + actual_count ++; + } + + ecs_strbuf_list_next(buf); + flecs_json_label(buf, world, variables[i].entity); + } + + if (actual_count) { + flecs_json_array_pop(buf); + } +} + +static +void flecs_json_serialize_iter_result_variable_ids( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + char **variable_names = it->variable_names; + ecs_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; + + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; + + if (!actual_count) { + flecs_json_memberl(buf, "var_ids"); + flecs_json_array_push(buf); + actual_count ++; + } + + ecs_strbuf_list_next(buf); + flecs_json_number(buf, (double)variables[i].entity); + } + + if (actual_count) { + flecs_json_array_pop(buf); + } +} + +static +bool flecs_json_serialize_iter_result_entity_names( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); + + EcsIdentifier *names = ecs_table_get_id(it->world, it->table, + ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); + if (!names) { + return false; + } + + int i; + for (i = 0; i < it->count; i ++) { + flecs_json_next(buf); + flecs_json_string(buf, names[i].value); + } + + return true; +} + +static +void flecs_json_serialize_iter_result_entity_ids( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + if (!it->count) { + return; + } + + flecs_json_memberl(buf, "entity_ids"); + flecs_json_array_push(buf); + + ecs_entity_t *entities = it->entities; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_number(buf, (double)(uint32_t)entities[i]); + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_iter_result_parent( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + ecs_table_t *table = it->table; + if (!(table->flags & EcsTableHasChildOf)) { + return; + } + + ecs_table_record_t *tr = flecs_id_record_get_table( + world->idr_childof_wildcard, it->table); + if (tr == NULL) { + return; + } + + ecs_id_t id = table->type.array[tr->index]; + ecs_entity_t parent = ecs_pair_second(world, id); + char *path = ecs_get_fullpath(world, parent); + flecs_json_memberl(buf, "parent"); + flecs_json_string(buf, path); + ecs_os_free(path); +} + +static +void flecs_json_serialize_iter_result_entities( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + if (!it->count) { + return; + } + + flecs_json_serialize_iter_result_parent(world, it, buf); + + flecs_json_memberl(buf, "entities"); + flecs_json_array_push(buf); + + if (!flecs_json_serialize_iter_result_entity_names(it, buf)) { + ecs_entity_t *entities = it->entities; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_number(buf, (double)(uint32_t)entities[i]); + } + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_iter_result_entity_labels( + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_json_ser_ctx_t *ser_ctx) +{ + (void)buf; + (void)ser_ctx; + if (!it->count) { + return; + } + + if (!ser_ctx->idr_doc_name) { + return; + } + +#ifdef FLECS_DOC + ecs_table_t *table = it->table; + ecs_table_record_t *tr = flecs_id_record_get_table( + ser_ctx->idr_doc_name, table); + if (tr == NULL) { + return; + } + + EcsDocDescription *labels = ecs_table_get_column( + table, tr->column, it->offset); + ecs_assert(labels != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_json_memberl(buf, "entity_labels"); + flecs_json_array_push(buf); + + int i; + for (i = 0; i < it->count; i ++) { + flecs_json_next(buf); + flecs_json_string_escape(buf, labels[i].value); + } + + flecs_json_array_pop(buf); +#endif +} + +static +void flecs_json_serialize_iter_result_colors( + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_json_ser_ctx_t *ser_ctx) +{ + (void)buf; + (void)ser_ctx; + + if (!it->count) { + return; + } + +#ifdef FLECS_DOC + if (!ser_ctx->idr_doc_color) { + return; + } + + ecs_table_record_t *tr = flecs_id_record_get_table( + ser_ctx->idr_doc_color, it->table); + if (tr == NULL) { + return; + } + + EcsDocDescription *colors = ecs_table_get_column( + it->table, tr->column, it->offset); + ecs_assert(colors != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_json_memberl(buf, "colors"); + flecs_json_array_push(buf); + + int i; + for (i = 0; i < it->count; i ++) { + flecs_json_next(buf); + flecs_json_string(buf, colors[i].value); + } + + flecs_json_array_pop(buf); +#endif +} + +static +int flecs_json_serialize_iter_result_values( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + if (!it->ptrs || (it->flags & EcsIterNoData)) { + return 0; + } + + flecs_json_memberl(buf, "values"); + flecs_json_array_push(buf); + + int32_t i, term_count = it->field_count; + for (i = 0; i < term_count; i ++) { + ecs_strbuf_list_next(buf); + + const void *ptr = NULL; + if (it->ptrs) { + ptr = it->ptrs[i]; + } + + if (!ptr) { + /* No data in column. Append 0 if this is not an optional term */ + if (ecs_field_is_set(it, i + 1)) { + ecs_strbuf_appendch(buf, '0'); + continue; + } + } + + if (ecs_field_is_writeonly(it, i + 1)) { + ecs_strbuf_appendch(buf, '0'); + continue; + } + + /* Get component id (can be different in case of pairs) */ + ecs_entity_t type = ecs_get_typeid(world, it->ids[i]); + if (!type) { + /* Odd, we have a ptr but no Component? Not the place of the + * serializer to complain about that. */ + ecs_strbuf_appendch(buf, '0'); + continue; + } + + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + /* Also odd, typeid but not a component? */ + ecs_strbuf_appendch(buf, '0'); + continue; + } + + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + /* Not odd, component just has no reflection data */ + ecs_strbuf_appendch(buf, '0'); + continue; + } + + /* If term is not set, append empty array. This indicates that the term + * could have had data but doesn't */ + if (!ecs_field_is_set(it, i + 1)) { + flecs_json_array_push(buf); + flecs_json_array_pop(buf); + continue; + } + + if (ecs_field_is_self(it, i + 1)) { + int32_t count = it->count; + if (array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser)) { + return -1; + } + } else { + if (array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser)) { + return -1; + } + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +static +int flecs_json_serialize_iter_result_columns( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_table_t *table = it->table; + if (!table || !table->column_count) { + return 0; + } + + flecs_json_memberl(buf, "values"); + flecs_json_array_push(buf); + + ecs_type_t *type = &table->type; + int32_t *column_map = table->column_map; + ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); + + for (int i = 0; i < type->count; i ++) { + int32_t storage_column = -1; + if (column_map) { + storage_column = column_map[i]; + } + + if (!desc->serialize_private) { + ecs_id_t id = type->array[i]; + ecs_entity_t e = id; + if (ECS_IS_PAIR(id)) { + e = ecs_pair_first(world, id); + } + if (ecs_owns_id(world, e, EcsPrivate)) { + continue; + } + } + + ecs_strbuf_list_next(buf); + + if (storage_column == -1) { + ecs_strbuf_appendch(buf, '0'); + continue; + } + + ecs_entity_t typeid = table->data.columns[storage_column].ti->component; + if (!typeid) { + ecs_strbuf_appendch(buf, '0'); + continue; + } + + const EcsComponent *comp = ecs_get(world, typeid, EcsComponent); + if (!comp) { + ecs_strbuf_appendch(buf, '0'); + continue; + } + + const EcsMetaTypeSerialized *ser = ecs_get( + world, typeid, EcsMetaTypeSerialized); + if (!ser) { + ecs_strbuf_appendch(buf, '0'); + continue; + } + + void *ptr = ecs_vec_first(&table->data.columns[storage_column].data); + if (array_to_json_buf_w_type_data(world, ptr, it->count, buf, comp, ser)) { + return -1; + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +static +int flecs_json_serialize_iter_result( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx) +{ + if (desc && desc->serialize_rows) { + if (flecs_json_serialize_iter_result_rows(world, it, buf, desc, ser_ctx)) { + return -1; + } + } else { + flecs_json_next(buf); + flecs_json_object_push(buf); + + /* Each result can be matched with different component ids. Add them to + * the result so clients know with which component an entity was matched */ + if (desc && desc->serialize_table) { + flecs_json_serialize_iter_result_table_type(world, it, buf, desc); + } else { + if (!desc || desc->serialize_ids) { + flecs_json_serialize_iter_result_ids(world, it, buf); + } + if (desc && desc->serialize_id_labels) { + flecs_json_serialize_iter_result_id_labels(world, it, buf); + } + } + + /* Include information on which entity the term is matched with */ + if (!desc || (desc->serialize_sources && !desc->serialize_table)) { + flecs_json_serialize_iter_result_sources(world, it, buf); + } + + /* Write variable values for current result */ + if (!desc || desc->serialize_variables) { + flecs_json_serialize_iter_result_variables(world, it, buf); + } + + /* Write labels for variables */ + if (desc && desc->serialize_variable_labels) { + flecs_json_serialize_iter_result_variable_labels(world, it, buf); + } + + /* Write ids for variables */ + if (desc && desc->serialize_variable_ids) { + flecs_json_serialize_iter_result_variable_ids(it, buf); + } + + /* Include information on which terms are set, to support optional terms */ + if (!desc || (desc->serialize_is_set && !desc->serialize_table)) { + flecs_json_serialize_iter_result_is_set(it, buf); + } + + /* Write entity ids for current result (for queries with This terms) */ + if (!desc || desc->serialize_entities) { + flecs_json_serialize_iter_result_entities(world, it, buf); + } + + /* Write ids for entities */ + if (desc && desc->serialize_entity_ids) { + flecs_json_serialize_iter_result_entity_ids(it, buf); + } + + /* Write labels for entities */ + if (desc && desc->serialize_entity_labels) { + flecs_json_serialize_iter_result_entity_labels(it, buf, ser_ctx); + } + + /* Write colors for entities */ + if (desc && desc->serialize_colors) { + flecs_json_serialize_iter_result_colors(it, buf, ser_ctx); + } + + /* Serialize component values */ + if (desc && desc->serialize_table) { + if (flecs_json_serialize_iter_result_columns(world, it, buf, desc)) { + return -1; + } + } else { + if (!desc || desc->serialize_values) { + if (flecs_json_serialize_iter_result_values(world, it, buf)) { + return -1; + } + } + } + + /* Add "alerts": true member if table has entities with active alerts */ +#ifdef FLECS_ALERTS + if (it->table && (ecs_id(EcsAlertsActive) != 0)) { + /* Only add field if alerts addon is imported */ + if (ecs_table_has_id(world, it->table, ecs_id(EcsAlertsActive))) { + flecs_json_memberl(buf, "alerts"); + flecs_json_true(buf); + } + } +#endif + + flecs_json_object_pop(buf); + } + + return 0; +} + +int ecs_iter_to_json_buf( + const ecs_world_t *world, + ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_time_t duration = {0}; + if (desc && desc->measure_eval_duration) { + ecs_time_measure(&duration); + } + + /* Cache id record for flecs.doc ids */ + ecs_json_ser_ctx_t ser_ctx; + ecs_os_zeromem(&ser_ctx); +#ifdef FLECS_DOC + ser_ctx.idr_doc_name = flecs_id_record_get(world, + ecs_pair_t(EcsDocDescription, EcsName)); + ser_ctx.idr_doc_color = flecs_id_record_get(world, + ecs_pair_t(EcsDocDescription, EcsDocColor)); +#endif + + flecs_json_object_push(buf); + + /* Serialize component ids of the terms (usually provided by query) */ + if (!desc || !desc->serialize_rows) { + if (!desc || desc->serialize_term_ids) { + flecs_json_serialize_iter_ids(world, it, buf); + } + + if (desc && desc->serialize_term_labels) { + flecs_json_serialize_iter_id_labels(world, it, buf); + } + + /* Serialize variable names, if iterator has any */ + flecs_json_serialize_iter_variables(it, buf); + } + + /* Serialize type info if enabled */ + if (desc && desc->serialize_type_info) { + flecs_json_serialize_type_info(world, it, buf); + } + + /* Serialize field info if enabled */ + if (desc && desc->serialize_field_info) { + flecs_json_serialize_field_info(world, it, buf, &ser_ctx); + } + + /* Serialize results */ + if (!desc || !desc->dont_serialize_results) { + flecs_json_memberl(buf, "results"); + flecs_json_array_push(buf); + + /* Use instancing for improved performance */ + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + /* If serializing entire table, don't bother letting the iterator populate + * data fields as we'll be iterating all columns. */ + if (desc && desc->serialize_table) { + ECS_BIT_SET(it->flags, EcsIterNoData); + } + + ecs_iter_next_action_t next = it->next; + while (next(it)) { + if (flecs_json_serialize_iter_result(world, it, buf, desc, &ser_ctx)) { + ecs_strbuf_reset(buf); + ecs_iter_fini(it); + return -1; + } + } + + flecs_json_array_pop(buf); + } else { + ecs_iter_fini(it); + } + + int32_t f, field_count = it->field_count; + if (desc && (desc->serialize_rows || desc->serialize_values)) { + for (f = 0; f < field_count; f ++) { + ecs_os_free(ser_ctx.value_ctx[f].id_label); + } + } + + if (desc && desc->measure_eval_duration) { + double dt = ecs_time_measure(&duration); + flecs_json_memberl(buf, "eval_duration"); + flecs_json_number(buf, dt); + } + + flecs_json_object_pop(buf); + + return 0; +} + +char* ecs_iter_to_json( + const ecs_world_t *world, + ecs_iter_t *it, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ecs_iter_to_json_buf(world, it, &buf, desc)) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +int ecs_world_to_json_buf( + ecs_world_t *world, + ecs_strbuf_t *buf_out, + const ecs_world_to_json_desc_t *desc) +{ + ecs_filter_t f = ECS_FILTER_INIT; + ecs_filter_desc_t filter_desc = {0}; + filter_desc.storage = &f; + + if (desc && desc->serialize_builtin && desc->serialize_modules) { + filter_desc.terms[0].id = EcsAny; + } else { + bool serialize_builtin = desc && desc->serialize_builtin; + bool serialize_modules = desc && desc->serialize_modules; + int32_t term_id = 0; + + if (!serialize_builtin) { + filter_desc.terms[term_id].id = ecs_pair(EcsChildOf, EcsFlecs); + filter_desc.terms[term_id].oper = EcsNot; + filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; + term_id ++; + } + if (!serialize_modules) { + filter_desc.terms[term_id].id = EcsModule; + filter_desc.terms[term_id].oper = EcsNot; + filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; + } + } + + if (ecs_filter_init(world, &filter_desc) == NULL) { + return -1; + } + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_iter_to_json_desc_t json_desc = { + .serialize_table = true, + .serialize_ids = true, + .serialize_entities = true, + .serialize_private = true + }; + + int ret = ecs_iter_to_json_buf(world, &it, buf_out, &json_desc); + ecs_filter_fini(&f); + return ret; +} + +char* ecs_world_to_json( + ecs_world_t *world, + const ecs_world_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ecs_world_to_json_buf(world, &buf, desc)) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +#endif + +/** + * @file json/serialize_iter_rows.c + * @brief Serialize (component) values to JSON strings. + */ + + +#ifdef FLECS_JSON + +static +bool flecs_json_serialize_row_tags( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t f, field_count = it->field_count; + ecs_id_t *ids = it->ids; + void **ptrs = it->ptrs; + + int32_t tag_count = 0; + for (f = 0; f < field_count; f ++) { + if (!ecs_field_is_set(it, f + 1)) { + continue; + } + + ecs_id_t id = ids[f]; + if (ECS_IS_PAIR(id)) { + continue; + } + + if (ptrs[f]) { + continue; /* Ignore components */ + } + + if (!tag_count) { + flecs_json_memberl(buf, "tags"); + flecs_json_array_push(buf); + } + + flecs_json_next(buf); + flecs_json_label(buf, world, id); + + tag_count ++; + } + + if (tag_count) { + flecs_json_array_pop(buf); + } + + return tag_count != 0; +} + +static +bool flecs_json_serialize_row_pairs( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + int32_t f, field_count = it->field_count; + ecs_id_t *ids = it->ids; + + int32_t pair_count = 0; + for (f = 0; f < field_count; f ++) { + if (!ecs_field_is_set(it, f + 1)) { + continue; + } + + ecs_id_t id = ids[f]; + if (!ECS_IS_PAIR(id)) { + continue; + } + + if (!pair_count) { + flecs_json_memberl(buf, "pairs"); + flecs_json_object_push(buf); + } + + flecs_json_next(buf); + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_second(world, id); + + flecs_json_label(buf, world, first); + ecs_strbuf_appendlit(buf, ":"); + flecs_json_label(buf, world, second); + + pair_count ++; + } + + if (pair_count) { + flecs_json_object_pop(buf); + } + + return pair_count != 0; +} + +static +bool flecs_json_serialize_row_vars( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + char **variable_names = it->variable_names; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; + + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; + + ecs_entity_t var = it->variables[i].entity; + if (!var) { + /* Can't happen, but not the place of the serializer to complain */ + continue; + } + + if (!actual_count) { + flecs_json_memberl(buf, "vars"); + flecs_json_object_push(buf); + actual_count ++; + } + + flecs_json_member(buf, var_name); + flecs_json_label(buf, world, var); + } + + if (actual_count) { + flecs_json_object_pop(buf); + } + + return actual_count != 0; +} + +static +bool flecs_json_serialize_tags_pairs_vars( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + bool result = false; + ecs_strbuf_list_push(buf, "", ","); + result |= flecs_json_serialize_row_tags(world, it, buf); + result |= flecs_json_serialize_row_pairs(world, it, buf); + result |= flecs_json_serialize_row_vars(world, it, buf); + result |= flecs_json_serialize_iter_result_is_set(it, buf); + ecs_strbuf_list_pop(buf, ""); + if (!result) { + ecs_strbuf_reset(buf); + } + return result; +} + +static +bool flecs_json_serialize_table_row_tags( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + int32_t f, type_count = table->type.count; + ecs_id_t *ids = table->type.array; + int32_t *column_map = table->column_map; + + int32_t tag_count = 0; + for (f = 0; f < type_count; f ++) { + ecs_id_t id = ids[f]; + if (ECS_IS_PAIR(id)) { + continue; + } + + if (column_map[f] != -1) { + continue; /* Ignore components */ + } + + if (!desc || !desc->serialize_private) { + if (ecs_has_id(world, id & ECS_COMPONENT_MASK, EcsPrivate)) { + continue; + } + } + + if (!tag_count) { + flecs_json_memberl(buf, "tags"); + flecs_json_array_push(buf); + } + + flecs_json_next(buf); + flecs_json_label(buf, world, id); + + tag_count ++; + } + + if (tag_count) { + flecs_json_array_pop(buf); + } + + return tag_count != 0; +} + +static +bool flecs_json_serialize_table_row_pairs( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + int32_t f, type_count = table->type.count; + ecs_id_t *ids = table->type.array; + + int32_t pair_count = 0; + bool same_first = false; + + for (f = 0; f < type_count; f ++) { + ecs_id_t id = ids[f]; + if (!ECS_IS_PAIR(id)) { + continue; + } + + ecs_entity_t first = flecs_entities_get_generation( + world, ECS_PAIR_FIRST(id)); + if (!desc || !desc->serialize_private) { + if (ecs_has_id(world, first, EcsPrivate)) { + continue; + } + } + + if (!pair_count) { + flecs_json_memberl(buf, "pairs"); + flecs_json_object_push(buf); + } + + ecs_entity_t second = flecs_entities_get_generation( + world, ECS_PAIR_SECOND(id)); + + bool is_last = f == (type_count - 1); + bool is_same = !is_last && + (ECS_PAIR_FIRST(ids[f + 1]) == ECS_PAIR_FIRST(id)); + + if (same_first && f && ECS_PAIR_FIRST(ids[f - 1]) != ECS_PAIR_FIRST(id)) { + /* New pair has different first elem, so close array */ + flecs_json_array_pop(buf); + same_first = false; + } + + if (!same_first) { + /* Only append pair label if we're not appending to array */ + flecs_json_next(buf); + flecs_json_label(buf, world, first); + ecs_strbuf_appendlit(buf, ":"); + + /* Open array scope if this is a pair with multiple targets */ + if (is_same) { + flecs_json_array_push(buf); + same_first = true; + } + } + if (same_first) { + flecs_json_next(buf); + } + + flecs_json_label(buf, world, second); + + pair_count ++; + } + + if (same_first) { + flecs_json_array_pop(buf); + } + + if (pair_count) { + flecs_json_object_pop(buf); + } + + return pair_count != 0; +} + +static +bool flecs_json_serialize_table_tags_pairs_vars( + const ecs_world_t *world, + ecs_table_t *table, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + bool result = false; + ecs_strbuf_list_push(buf, "", ","); + result |= flecs_json_serialize_table_row_tags(world, table, buf, desc); + result |= flecs_json_serialize_table_row_pairs(world, table, buf, desc); + ecs_strbuf_list_pop(buf, ""); + if (!result) { + ecs_strbuf_reset(buf); + } + return result; +} + +static +bool flecs_json_serialize_get_value_ctx( + const ecs_world_t *world, + ecs_id_t id, + ecs_json_value_ser_ctx_t *ctx) +{ + if (!ctx->initialized) { + ctx->initialized = true; + + ecs_strbuf_t idlbl = ECS_STRBUF_INIT; + flecs_json_id_member(&idlbl, world, id); + ctx->id_label = ecs_strbuf_get(&idlbl); + + ecs_entity_t type = ecs_get_typeid(world, id); + if (!type) { + return false; + } + + ctx->type = type; + ctx->ser = ecs_get(world, type, EcsMetaTypeSerialized); + if (!ctx->ser) { + return false; + } + + return true; + } else { + return ctx->ser != NULL; + } +} + +bool flecs_json_serialize_get_field_ctx( + const ecs_world_t *world, + const ecs_iter_t *it, + int32_t f, + ecs_json_ser_ctx_t *ser_ctx) +{ + ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; + if (it->query) { + return flecs_json_serialize_get_value_ctx( + world, it->query->ids[f], value_ctx); + } else if (it->ids[f]) { + return flecs_json_serialize_get_value_ctx(world, it->ids[f], value_ctx); + } else { + return false; + } +} + +static +int flecs_json_serialize_row_components( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ser_ctx, + int32_t row) +{ + void **ptrs = it->ptrs; + if (!ptrs) { + return 0; + } + + int32_t f, field_count = it->field_count, component_count = 0; + for (f = 0; f < field_count; f ++) { + if (!ecs_field_is_set(it, f + 1)) { + continue; + } + + void *ptr = ptrs[f]; + if (!ptr) { + continue; + } + + if (ecs_field_is_self(it, f + 1)) { + ptr = ECS_ELEM(ptr, it->sizes[f], row); + } + + ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; + if (!flecs_json_serialize_get_value_ctx(world, it->ids[f], value_ctx)) { + continue; + } + + if (!component_count) { + flecs_json_memberl(buf, "components"); + flecs_json_object_push(buf); + } + + flecs_json_member(buf, value_ctx->id_label); + if (json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { + return -1; + } + + component_count ++; + } + + if (component_count) { + flecs_json_object_pop(buf); + } + + return 0; +} + +static +int flecs_json_serialize_row_table_components( + const ecs_world_t *world, + ecs_table_t *table, + ecs_strbuf_t *buf, + ecs_json_value_ser_ctx_t *values_ctx, + int32_t row, + int32_t column_count) +{ + int32_t f, component_count = 0; + for (f = 0; f < column_count; f ++) { + ecs_column_t *column = &table->data.columns[f]; + ecs_json_value_ser_ctx_t *value_ctx = &values_ctx[f]; + if (!flecs_json_serialize_get_value_ctx(world, column->id, value_ctx)) { + continue; + } + + if (!component_count) { + flecs_json_memberl(buf, "components"); + flecs_json_object_push(buf); + } + + void *ptr = ecs_vec_get(&column->data, column->size, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value_ctx->id_label != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_json_member(buf, value_ctx->id_label); + if (json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { + return -1; + } + + component_count ++; + } + + if (component_count) { + flecs_json_object_pop(buf); + } + + return 0; +} + +static +void flecs_json_serialize_iter_this_row( + const ecs_iter_t *it, + const char *parent_path, + const EcsIdentifier *names, + int32_t row, + ecs_strbuf_t *buf) +{ + if (parent_path) { + flecs_json_memberl(buf, "parent"); + flecs_json_string(buf, parent_path); + } + + flecs_json_memberl(buf, "name"); + if (names) { + flecs_json_string(buf, names[row].value); + } else { + ecs_strbuf_appendlit(buf, "\""); + ecs_strbuf_appendint(buf, flecs_uto(int32_t, it->entities[row])); + ecs_strbuf_appendlit(buf, "\""); + } +} + +static +int flecs_json_serialize_iter_result_query_rows( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ser_ctx, + int32_t count, + bool has_this, + const char *parent_path, + const EcsIdentifier *names) +{ + /* Serialize tags, pairs, vars once, since they're the same for each row */ + ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; + int32_t tags_pairs_vars_len = 0; + char *tags_pairs_vars = NULL; + if (flecs_json_serialize_tags_pairs_vars(world, it, &tags_pairs_vars_buf)) { + tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); + tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); + } + + int32_t i; + for (i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_object_push(buf); + + if (has_this) { + flecs_json_serialize_iter_this_row( + it, parent_path, names, i, buf); + } + + if (tags_pairs_vars) { + ecs_strbuf_list_appendstrn(buf, + tags_pairs_vars, tags_pairs_vars_len); + } + + if (flecs_json_serialize_row_components(world, it, buf, ser_ctx, i)) { + return -1; + } + + flecs_json_object_pop(buf); + } + + ecs_os_free(tags_pairs_vars); + + return 0; +} + +static +int flecs_json_serialize_iter_result_table_rows( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + int32_t count, + bool has_this, + const char *parent_path, + const EcsIdentifier *names) +{ + ecs_table_t *table = it->table; + if (!table || !count) { + return 0; + } + + /* Serialize tags, pairs, vars once, since they're the same for each row */ + ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; + int32_t tags_pairs_vars_len = 0; + char *tags_pairs_vars = NULL; + if (flecs_json_serialize_table_tags_pairs_vars( + world, table, &tags_pairs_vars_buf, desc)) + { + tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); + tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); + } + + /* If one entity has more than 256 components (oof), bad luck */ + ecs_json_value_ser_ctx_t values_ctx[256] = {{0}}; + int32_t column_count = table->column_count; + if (column_count > 256) { + column_count = 256; + } + + int32_t i, end = it->offset + count; + int result = 0; + for (i = it->offset; i < end; i ++) { + flecs_json_next(buf); + flecs_json_object_push(buf); + + if (has_this) { + flecs_json_serialize_iter_this_row( + it, parent_path, &names[-it->offset], i, buf); + } + + if (tags_pairs_vars) { + ecs_strbuf_list_appendstrn(buf, + tags_pairs_vars, tags_pairs_vars_len); + } + + if (flecs_json_serialize_row_table_components( + world, table, buf, values_ctx, i, column_count)) + { + result = -1; + break; + } + + flecs_json_object_pop(buf); + } + + for (i = 0; i < column_count; i ++) { + ecs_os_free(values_ctx[i].id_label); + } + + ecs_os_free(tags_pairs_vars); + + return result; +} + +int flecs_json_serialize_iter_result_rows( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx) +{ + char *parent_path = NULL; + EcsIdentifier *names = NULL; + + int32_t count = it->count; + bool has_this = true; + if (!count) { + count = 1; /* Query without this variable */ + has_this = false; + } else { + ecs_table_t *table = it->table; + if (table) { + /* Get path to parent once for entire table */ + if (table->flags & EcsTableHasChildOf) { + const ecs_table_record_t *tr = flecs_table_record_get( + world, table, ecs_pair(EcsChildOf, EcsWildcard)); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t parent = ecs_pair_second( + world, table->type.array[tr->index]); + parent_path = ecs_get_fullpath(world, parent); + } + + /* Fetch name column once vs. calling ecs_get_name for each row */ + if (table->flags & EcsTableHasName) { + names = ecs_table_get_id(it->world, it->table, + ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); + } + } else { + /* Very rare case, but could happen if someone's using an iterator + * to return empty entities. */ + } + } + + if (!desc->serialize_table) { + flecs_json_serialize_iter_result_query_rows( + world, it, buf, ser_ctx, count, has_this, parent_path, names); + } else { + flecs_json_serialize_iter_result_table_rows( + world, it, buf, desc, count, has_this, parent_path, names); + } + + ecs_os_free(parent_path); + + return 0; +} + +#endif + +/** + * @file json/serialize_type_info.c + * @brief Serialize type (reflection) information to JSON. + */ + + +#ifdef FLECS_JSON + +static +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf); + +static +int json_typeinfo_ser_primitive( + ecs_primitive_kind_t kind, + ecs_strbuf_t *str) +{ + switch(kind) { + case EcsBool: + flecs_json_string(str, "bool"); + break; + case EcsChar: + case EcsString: + flecs_json_string(str, "text"); + break; + case EcsByte: + flecs_json_string(str, "byte"); + break; + case EcsU8: + case EcsU16: + case EcsU32: + case EcsU64: + case EcsI8: + case EcsI16: + case EcsI32: + case EcsI64: + case EcsIPtr: + case EcsUPtr: + flecs_json_string(str, "int"); + break; + case EcsF32: + case EcsF64: + flecs_json_string(str, "float"); + break; + case EcsEntity: + flecs_json_string(str, "entity"); + break; + case EcsId: + flecs_json_string(str, "id"); + break; + default: + return -1; + } + + return 0; +} + +static +void json_typeinfo_ser_constants( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_pair(EcsChildOf, type) + }); + + while (ecs_term_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + flecs_json_next(str); + flecs_json_string(str, ecs_get_name(world, it.entities[i])); + } + } +} + +static +void json_typeinfo_ser_enum( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"enum\""); + json_typeinfo_ser_constants(world, type, str); +} + +static +void json_typeinfo_ser_bitmask( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"bitmask\""); + json_typeinfo_ser_constants(world, type, str); +} + +static +int json_typeinfo_ser_array( + const ecs_world_t *world, + ecs_entity_t elem_type, + int32_t count, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"array\""); + + flecs_json_next(str); + if (json_typeinfo_ser_type(world, elem_type, str)) { + goto error; + } + + ecs_strbuf_list_append(str, "%u", count); + return 0; +error: + return -1; +} + +static +int json_typeinfo_ser_array_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + const EcsArray *arr = ecs_get(world, type, EcsArray); + ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); + if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { + goto error; + } + + return 0; +error: + return -1; +} + +static +int json_typeinfo_ser_vector( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + const EcsVector *arr = ecs_get(world, type, EcsVector); + ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_strbuf_list_appendstr(str, "\"vector\""); + + flecs_json_next(str); + if (json_typeinfo_ser_type(world, arr->type, str)) { + goto error; + } + + return 0; +error: + return -1; +} + +/* Serialize unit information */ +static +int json_typeinfo_ser_unit( + const ecs_world_t *world, + ecs_strbuf_t *str, + ecs_entity_t unit) +{ + flecs_json_memberl(str, "unit"); + flecs_json_path(str, world, unit); + + const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); + if (uptr) { + if (uptr->symbol) { + flecs_json_memberl(str, "symbol"); + flecs_json_string(str, uptr->symbol); + } + ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); + if (quantity) { + flecs_json_memberl(str, "quantity"); + flecs_json_path(str, world, quantity); + } + } + + return 0; +} + +static +void json_typeinfo_ser_range( + ecs_strbuf_t *str, + const char *kind, + ecs_member_value_range_t *range) +{ + flecs_json_member(str, kind); + flecs_json_array_push(str); + flecs_json_next(str); + flecs_json_number(str, range->min); + flecs_json_next(str); + flecs_json_number(str, range->max); + flecs_json_array_pop(str); +} + +/* Forward serialization to the different type kinds */ +static +int json_typeinfo_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + ecs_strbuf_t *str, + const EcsStruct *st) +{ + if (op->kind == EcsOpOpaque) { + const EcsOpaque *ct = ecs_get(world, op->type, + EcsOpaque); + ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL); + return json_typeinfo_ser_type(world, ct->as_type, str); + } + + flecs_json_array_push(str); + + switch(op->kind) { + case EcsOpPush: + case EcsOpPop: + /* Should not be parsed as single op */ + ecs_throw(ECS_INVALID_PARAMETER, NULL); + break; + case EcsOpEnum: + json_typeinfo_ser_enum(world, op->type, str); + break; + case EcsOpBitmask: + json_typeinfo_ser_bitmask(world, op->type, str); + break; + case EcsOpArray: + json_typeinfo_ser_array_type(world, op->type, str); + break; + case EcsOpVector: + json_typeinfo_ser_vector(world, op->type, str); + break; + case EcsOpOpaque: + /* Can't happen, already handled above */ + ecs_throw(ECS_INTERNAL_ERROR, NULL); + break; + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + case EcsOpString: + if (json_typeinfo_ser_primitive( + flecs_json_op_to_primitive_kind(op->kind), str)) + { + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + break; + case EcsOpScope: + case EcsOpPrimitive: + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + + if (st) { + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + bool value_range = ECS_NEQ(m->range.min, m->range.max); + bool error_range = ECS_NEQ(m->error_range.min, m->error_range.max); + bool warning_range = ECS_NEQ(m->warning_range.min, m->warning_range.max); + + ecs_entity_t unit = m->unit; + if (unit || error_range || warning_range || value_range) { + flecs_json_next(str); + flecs_json_next(str); + flecs_json_object_push(str); + + if (unit) { + json_typeinfo_ser_unit(world, str, unit); + } + if (value_range) { + json_typeinfo_ser_range(str, "range", &m->range); + } + if (error_range) { + json_typeinfo_ser_range(str, "error_range", &m->error_range); + } + if (warning_range) { + json_typeinfo_ser_range(str, "warning_range", &m->warning_range); + } + + flecs_json_object_pop(str); + } + } + + flecs_json_array_pop(str); + + return 0; +error: + return -1; +} + +/* Iterate over a slice of the type ops array */ +static +int json_typeinfo_ser_type_ops( + const ecs_world_t *world, + ecs_meta_type_op_t *ops, + int32_t op_count, + ecs_strbuf_t *str, + const EcsStruct *st) +{ + const EcsStruct *stack[64] = {st}; + int32_t sp = 1; + + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; + + if (op != ops) { + if (op->name) { + flecs_json_member(str, op->name); + } + } + + int32_t elem_count = op->count; + if (elem_count > 1) { + flecs_json_array_push(str); + json_typeinfo_ser_array(world, op->type, op->count, str); + flecs_json_array_pop(str); + i += op->op_count - 1; + continue; + } + + switch(op->kind) { + case EcsOpPush: + flecs_json_object_push(str); + ecs_assert(sp < 63, ECS_INVALID_OPERATION, "type nesting too deep"); + stack[sp ++] = ecs_get(world, op->type, EcsStruct); + break; + case EcsOpPop: + flecs_json_object_pop(str); + sp --; + break; + case EcsOpArray: + case EcsOpVector: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + case EcsOpString: + case EcsOpOpaque: + if (json_typeinfo_ser_type_op(world, op, str, stack[sp - 1])) { + goto error; + } + break; + case EcsOpPrimitive: + case EcsOpScope: + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + } + + return 0; +error: + return -1; +} + +static +int json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) +{ + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + ecs_strbuf_appendch(buf, '0'); + return 0; + } + + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + ecs_strbuf_appendch(buf, '0'); + return 0; + } + + const EcsStruct *st = ecs_get(world, type, EcsStruct); + ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); + int32_t count = ecs_vec_count(&ser->ops); + + return json_typeinfo_ser_type_ops(world, ops, count, buf, st); +} + +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) +{ + return json_typeinfo_ser_type(world, type, buf); +} + +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_type_info_to_json_buf(world, type, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +#endif + +/** + * @file meta/api.c + * @brief API for creating entities with reflection data. + */ + +/** + * @file meta/meta.h + * @brief Private functions for meta addon. + */ + +#ifndef FLECS_META_PRIVATE_H +#define FLECS_META_PRIVATE_H + + +#ifdef FLECS_META + +void ecs_meta_type_serialized_init( + ecs_iter_t *it); + +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr); + +ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind( + ecs_primitive_kind_t kind); + +bool flecs_unit_validate( + ecs_world_t *world, + ecs_entity_t t, + EcsUnit *data); + +#endif + +#endif + + +#ifdef FLECS_META + +static +bool flecs_type_is_number( + ecs_world_t *world, + ecs_entity_t type) +{ + const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); + if (!p) { + return false; + } + + switch(p->kind) { + case EcsChar: + case EcsU8: + case EcsU16: + case EcsU32: + case EcsU64: + case EcsI8: + case EcsI16: + case EcsI32: + case EcsI64: + case EcsF32: + case EcsF64: + return true; + + case EcsBool: + case EcsByte: + case EcsUPtr: + case EcsIPtr: + case EcsString: + case EcsEntity: + case EcsId: + return false; + default: + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } +} + +ecs_entity_t ecs_primitive_init( + ecs_world_t *world, + const ecs_primitive_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_set(world, t, EcsPrimitive, { desc->kind }); + + flecs_resume_readonly(world, &rs); + return t; +} + +ecs_entity_t ecs_enum_init( + ecs_world_t *world, + const ecs_enum_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_add(world, t, EcsEnum); + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_enum_constant_t *m_desc = &desc->constants[i]; + if (!m_desc->name) { + break; + } + + ecs_entity_t c = ecs_entity(world, { + .name = m_desc->name + }); + + if (!m_desc->value) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, + {m_desc->value}); + } + } + + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); + + if (i == 0) { + ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; + } + + return t; +} + +ecs_entity_t ecs_bitmask_init( + ecs_world_t *world, + const ecs_bitmask_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_add(world, t, EcsBitmask); + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; + if (!m_desc->name) { + break; + } + + ecs_entity_t c = ecs_entity(world, { + .name = m_desc->name + }); + + if (!m_desc->value) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, + {m_desc->value}); + } + } + + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); + + if (i == 0) { + ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; + } + + return t; +} + +ecs_entity_t ecs_array_init( + ecs_world_t *world, + const ecs_array_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_set(world, t, EcsArray, { + .type = desc->type, + .count = desc->count + }); + + flecs_resume_readonly(world, &rs); + + return t; +} + +ecs_entity_t ecs_vector_init( + ecs_world_t *world, + const ecs_vector_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_set(world, t, EcsVector, { + .type = desc->type + }); + + flecs_resume_readonly(world, &rs); + + return t; +} + +static +bool flecs_member_range_overlaps( + const ecs_member_value_range_t *range, + const ecs_member_value_range_t *with) +{ + if (ECS_EQ(with->min, with->max)) { + return false; + } + + if (ECS_EQ(range->min, range->max)) { + return false; + } + + if (range->min < with->min || + range->max > with->max) + { + return true; + } + + return false; +} + +ecs_entity_t ecs_struct_init( + ecs_world_t *world, + const ecs_struct_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_member_t *m_desc = &desc->members[i]; + if (!m_desc->type) { + break; + } + + if (!m_desc->name) { + ecs_err("member %d of struct '%s' does not have a name", i, + ecs_get_name(world, t)); + goto error; + } + + ecs_entity_t m = ecs_entity(world, { + .name = m_desc->name + }); + + ecs_set(world, m, EcsMember, { + .type = m_desc->type, + .count = m_desc->count, + .offset = m_desc->offset, + .unit = m_desc->unit + }); + + EcsMemberRanges *ranges = NULL; + const ecs_member_value_range_t *range = &m_desc->range; + const ecs_member_value_range_t *error = &m_desc->error_range; + const ecs_member_value_range_t *warning = &m_desc->warning_range; + if (ECS_NEQ(range->min, range->max)) { + ranges = ecs_get_mut(world, m, EcsMemberRanges); + if (range->min > range->max) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an invalid value range [%f..%f]", + member_name, range->min, range->max); + ecs_os_free(member_name); + goto error; + } + ranges->value.min = range->min; + ranges->value.max = range->max; + } + if (ECS_NEQ(error->min, error->max)) { + if (error->min > error->max) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an invalid error range [%f..%f]", + member_name, error->min, error->max); + ecs_os_free(member_name); + goto error; + } + if (flecs_member_range_overlaps(error, range)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("error range of member '%s' overlaps with value range", + member_name); + ecs_os_free(member_name); + goto error; + } + if (!ranges) { + ranges = ecs_get_mut(world, m, EcsMemberRanges); + } + ranges->error.min = error->min; + ranges->error.max = error->max; + } + + if (ECS_NEQ(warning->min, warning->max)) { + if (warning->min > warning->max) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an invalid warning range [%f..%f]", + member_name, warning->min, warning->max); + ecs_os_free(member_name); + goto error; + } + if (flecs_member_range_overlaps(warning, range)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("warning range of member '%s' overlaps with value " + "range", member_name); + ecs_os_free(member_name); + goto error; + } + if (flecs_member_range_overlaps(warning, error)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("warning range of member '%s' overlaps with error " + "range", member_name); + ecs_os_free(member_name); + goto error; + } + + if (!ranges) { + ranges = ecs_get_mut(world, m, EcsMemberRanges); + } + ranges->warning.min = warning->min; + ranges->warning.max = warning->max; + } + + if (ranges && !flecs_type_is_number(world, m_desc->type)) { + char *member_name = ecs_get_fullpath(world, m); + ecs_err("member '%s' has an value/error/warning range, but is not a " + "number", member_name); + ecs_os_free(member_name); + goto error; + } + + if (ranges) { + ecs_modified(world, m, EcsMemberRanges); + } + } + + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); + + if (i == 0) { + ecs_err("struct '%s' has no members", ecs_get_name(world, t)); + goto error; + } + + if (!ecs_has(world, t, EcsStruct)) { + goto error; + } + + return t; +error: + flecs_resume_readonly(world, &rs); + if (t) { + ecs_delete(world, t); + } + return 0; +} + +ecs_entity_t ecs_opaque_init( + ecs_world_t *world, + const ecs_opaque_desc_t *desc) +{ + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_set_ptr(world, t, EcsOpaque, &desc->type); + + flecs_resume_readonly(world, &rs); + + return t; +} + +ecs_entity_t ecs_unit_init( + ecs_world_t *world, + const ecs_unit_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_entity_t quantity = desc->quantity; + if (quantity) { + if (!ecs_has_id(world, quantity, EcsQuantity)) { + ecs_err("entity '%s' for unit '%s' is not a quantity", + ecs_get_name(world, quantity), ecs_get_name(world, t)); + goto error; + } + + ecs_add_pair(world, t, EcsQuantity, desc->quantity); + } else { + ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); + } + + EcsUnit *value = ecs_get_mut(world, t, EcsUnit); + value->base = desc->base; + value->over = desc->over; + value->translation = desc->translation; + value->prefix = desc->prefix; + ecs_os_strset(&value->symbol, desc->symbol); + + if (!flecs_unit_validate(world, t, value)) { + goto error; + } + + ecs_modified(world, t, EcsUnit); + + flecs_resume_readonly(world, &rs); + return t; +error: + if (t) { + ecs_delete(world, t); + } + flecs_resume_readonly(world, &rs); + return 0; +} + +ecs_entity_t ecs_unit_prefix_init( + ecs_world_t *world, + const ecs_unit_prefix_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } + + ecs_set(world, t, EcsUnitPrefix, { + .symbol = ECS_CONST_CAST(char*, desc->symbol), + .translation = desc->translation + }); + + flecs_resume_readonly(world, &rs); + + return t; +} + +ecs_entity_t ecs_quantity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); + + ecs_entity_t t = ecs_entity_init(world, desc); + if (!t) { + return 0; + } + + ecs_add_id(world, t, EcsQuantity); + + flecs_resume_readonly(world, &rs); + + return t; +} + +#endif + +/** + * @file meta/cursor.c + * @brief API for assigning values of runtime types with reflection. + */ + +#include + +#ifdef FLECS_META +#ifdef FLECS_PARSER +#endif + +static +const char* flecs_meta_op_kind_str( + ecs_meta_type_op_kind_t kind) +{ + switch(kind) { + + case EcsOpEnum: return "Enum"; + case EcsOpBitmask: return "Bitmask"; + case EcsOpArray: return "Array"; + case EcsOpVector: return "Vector"; + case EcsOpOpaque: return "Opaque"; + case EcsOpPush: return "Push"; + case EcsOpPop: return "Pop"; + case EcsOpPrimitive: return "Primitive"; + case EcsOpBool: return "Bool"; + case EcsOpChar: return "Char"; + case EcsOpByte: return "Byte"; + case EcsOpU8: return "U8"; + case EcsOpU16: return "U16"; + case EcsOpU32: return "U32"; + case EcsOpU64: return "U64"; + case EcsOpI8: return "I8"; + case EcsOpI16: return "I16"; + case EcsOpI32: return "I32"; + case EcsOpI64: return "I64"; + case EcsOpF32: return "F32"; + case EcsOpF64: return "F64"; + case EcsOpUPtr: return "UPtr"; + case EcsOpIPtr: return "IPtr"; + case EcsOpString: return "String"; + case EcsOpEntity: return "Entity"; + case EcsOpId: return "Id"; + case EcsOpScope: return "Scope"; + default: return "<< invalid kind >>"; + } +} + +/* Get current scope */ +static +ecs_meta_scope_t* flecs_meta_cursor_get_scope( + const ecs_meta_cursor_t *cursor) +{ + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + return ECS_CONST_CAST(ecs_meta_scope_t*, &cursor->scope[cursor->depth]); +error: + return NULL; +} + +/* Restore scope, if dotmember was used */ +static +ecs_meta_scope_t* flecs_meta_cursor_restore_scope( + ecs_meta_cursor_t *cursor, + const ecs_meta_scope_t* scope) +{ + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); + if (scope->prev_depth) { + cursor->depth = scope->prev_depth; + } +error: + return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; +} + +/* Get current operation for scope */ +static +ecs_meta_type_op_t* flecs_meta_cursor_get_op( + ecs_meta_scope_t *scope) +{ + ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, NULL); + return &scope->ops[scope->op_cur]; +} + +/* Get component for type in current scope */ +static +const EcsComponent* get_ecs_component( + const ecs_world_t *world, + ecs_meta_scope_t *scope) +{ + const EcsComponent *comp = scope->comp; + if (!comp) { + comp = scope->comp = ecs_get(world, scope->type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + } + return comp; +} + +/* Get size for type in current scope */ +static +ecs_size_t get_size( + const ecs_world_t *world, + ecs_meta_scope_t *scope) +{ + return get_ecs_component(world, scope)->size; +} + +static +int32_t get_elem_count( + ecs_meta_scope_t *scope) +{ + const EcsOpaque *opaque = scope->opaque; + + if (scope->vector) { + return ecs_vec_count(scope->vector); + } else if (opaque && opaque->count) { + return flecs_uto(int32_t, opaque->count(scope[-1].ptr)); + } + + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + return op->count; +} + +/* Get pointer to current field/element */ +static +ecs_meta_type_op_t* flecs_meta_cursor_get_ptr( + const ecs_world_t *world, + ecs_meta_scope_t *scope) +{ + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + ecs_size_t size = get_size(world, scope); + const EcsOpaque *opaque = scope->opaque; + + if (scope->vector) { + ecs_vec_set_min_count(NULL, scope->vector, size, scope->elem_cur + 1); + scope->ptr = ecs_vec_first(scope->vector); + } else if (opaque) { + if (scope->is_collection) { + if (!opaque->ensure_element) { + char *str = ecs_get_fullpath(world, scope->type); + ecs_err("missing ensure_element for opaque type %s", str); + ecs_os_free(str); + return NULL; + } + scope->is_empty_scope = false; + + void *opaque_ptr = opaque->ensure_element( + scope->ptr, flecs_ito(size_t, scope->elem_cur)); + ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, + "ensure_element returned NULL"); + return opaque_ptr; + } else if (op->name) { + if (!opaque->ensure_member) { + char *str = ecs_get_fullpath(world, scope->type); + ecs_err("missing ensure_member for opaque type %s", str); + ecs_os_free(str); + return NULL; + } + ecs_assert(scope->ptr != NULL, ECS_INTERNAL_ERROR, NULL); + return opaque->ensure_member(scope->ptr, op->name); + } else { + ecs_err("invalid operation for opaque type"); + return NULL; + } + } + + return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); +} + +static +int flecs_meta_cursor_push_type( + const ecs_world_t *world, + ecs_meta_scope_t *scope, + ecs_entity_t type, + void *ptr) +{ + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (ser == NULL) { + char *str = ecs_id_str(world, type); + ecs_err("cannot open scope for entity '%s' which is not a type", str); + ecs_os_free(str); + return -1; + } + + scope[0] = (ecs_meta_scope_t) { + .type = type, + .ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t), + .op_count = ecs_vec_count(&ser->ops), + .ptr = ptr + }; + + return 0; +} + +ecs_meta_cursor_t ecs_meta_cursor( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_meta_cursor_t result = { + .world = world, + .valid = true + }; + + if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) != 0) { + result.valid = false; + } + + return result; +error: + return (ecs_meta_cursor_t){ 0 }; +} + +void* ecs_meta_get_ptr( + ecs_meta_cursor_t *cursor) +{ + return flecs_meta_cursor_get_ptr(cursor->world, + flecs_meta_cursor_get_scope(cursor)); +} + +int ecs_meta_next( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + scope = flecs_meta_cursor_restore_scope(cursor, scope); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + + if (scope->is_collection) { + scope->elem_cur ++; + scope->op_cur = 0; + + if (scope->opaque) { + return 0; + } + + if (scope->elem_cur >= get_elem_count(scope)) { + ecs_err("out of collection bounds (%d)", scope->elem_cur); + return -1; + } + return 0; + } + + scope->op_cur += op->op_count; + + if (scope->op_cur >= scope->op_count) { + ecs_err("out of bounds"); + return -1; + } + + return 0; +} + +int ecs_meta_elem( + ecs_meta_cursor_t *cursor, + int32_t elem) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + if (!scope->is_collection) { + ecs_err("ecs_meta_elem can be used for collections only"); + return -1; + } + + scope->elem_cur = elem; + scope->op_cur = 0; + + if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { + ecs_err("out of collection bounds (%d)", scope->elem_cur); + return -1; + } + + return 0; +} + +int ecs_meta_member( + ecs_meta_cursor_t *cursor, + const char *name) +{ + if (cursor->depth == 0) { + ecs_err("cannot move to member in root scope"); + return -1; + } + + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + scope = flecs_meta_cursor_restore_scope(cursor, scope); + + ecs_hashmap_t *members = scope->members; + const ecs_world_t *world = cursor->world; + + if (!members) { + ecs_err("cannot move to member '%s' for non-struct type", name); + return -1; + } + + const uint64_t *cur_ptr = flecs_name_index_find_ptr(members, name, 0, 0); + if (!cur_ptr) { + char *path = ecs_get_fullpath(world, scope->type); + ecs_err("unknown member '%s' for type '%s'", name, path); + ecs_os_free(path); + return -1; + } + + scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); + + const EcsOpaque *opaque = scope->opaque; + if (opaque) { + if (!opaque->ensure_member) { + char *str = ecs_get_fullpath(world, scope->type); + ecs_err("missing ensure_member for opaque type %s", str); + ecs_os_free(str); + } + } + + return 0; +} + +int ecs_meta_dotmember( + ecs_meta_cursor_t *cursor, + const char *name) +{ +#ifdef FLECS_PARSER + ecs_meta_scope_t *cur_scope = flecs_meta_cursor_get_scope(cursor); + flecs_meta_cursor_restore_scope(cursor, cur_scope); + + int32_t prev_depth = cursor->depth; + int dotcount = 0; + + char token[ECS_MAX_TOKEN_SIZE]; + const char *ptr = name; + while ((ptr = ecs_parse_token(NULL, NULL, ptr, token, '.'))) { + if (ptr[0] != '.' && ptr[0]) { + ecs_parser_error(NULL, name, ptr - name, + "expected '.' or end of string"); + goto error; + } + + if (dotcount) { + ecs_meta_push(cursor); + } + + if (ecs_meta_member(cursor, token)) { + goto error; + } + + if (!ptr[0]) { + break; + } + + ptr ++; /* Skip . */ + + dotcount ++; + } + + cur_scope = flecs_meta_cursor_get_scope(cursor); + if (dotcount) { + cur_scope->prev_depth = prev_depth; + } + + return 0; +error: + return -1; +#else + (void)cursor; + (void)name; + ecs_err("the FLECS_PARSER addon is required for ecs_meta_dotmember"); + return -1; +#endif +} + +int ecs_meta_push( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + const ecs_world_t *world = cursor->world; + + if (cursor->depth == 0) { + if (!cursor->is_primitive_scope) { + if ((op->kind > EcsOpScope) && (op->count <= 1)) { + cursor->is_primitive_scope = true; + return 0; + } + } + } + + void *ptr = flecs_meta_cursor_get_ptr(world, scope); + cursor->depth ++; + ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, + ECS_INVALID_PARAMETER, NULL); + + ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); + + /* If we're not already in an inline array and this operation is an inline + * array, push a frame for the array. + * Doing this first ensures that inline arrays take precedence over other + * kinds of push operations, such as for a struct element type. */ + if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { + /* Push a frame just for the element type, with inline_array = true */ + next_scope[0] = (ecs_meta_scope_t){ + .ops = op, + .op_count = op->op_count, + .ptr = scope->ptr, + .type = op->type, + .is_collection = true, + .is_inline_array = true + }; + + /* With 'is_inline_array' set to true we ensure that we can never push + * the same inline array twice */ + return 0; + } + + /* Operation-specific switch behavior */ + switch(op->kind) { + + /* Struct push: this happens when pushing a struct member. */ + case EcsOpPush: { + const EcsOpaque *opaque = scope->opaque; + if (opaque) { + /* If this is a nested push for an opaque type, push the type of the + * element instead of the next operation. This ensures that we won't + * use flattened offsets for nested members. */ + if (flecs_meta_cursor_push_type( + world, next_scope, op->type, ptr) != 0) + { + goto error; + } + + /* Strip the Push operation since we already pushed */ + next_scope->members = next_scope->ops[0].members; + next_scope->ops = &next_scope->ops[1]; + next_scope->op_count --; + break; + } + + /* The ops array contains a flattened list for all members and nested + * members of a struct, so we can use (ops + 1) to initialize the ops + * array of the next scope. */ + next_scope[0] = (ecs_meta_scope_t) { + .ops = &op[1], /* op after push */ + .op_count = op->op_count - 1, /* don't include pop */ + .ptr = scope->ptr, + .type = op->type, + .members = op->members + }; + break; + } + + /* Array push for an array type. Arrays can be encoded in 2 ways: either by + * setting the EcsMember::count member to a value >1, or by specifying an + * array type as member type. This is the latter case. */ + case EcsOpArray: { + if (flecs_meta_cursor_push_type(world, next_scope, op->type, ptr) != 0) { + goto error; + } + + const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; + } + + /* Vector push */ + case EcsOpVector: { + next_scope->vector = ptr; + if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { + goto error; + } + + const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; + } + + /* Opaque type push. Depending on the type the opaque type represents the + * scope will be pushed as a struct or collection type. The type information + * of the as_type is retained, as this is important for type checking and + * for nested opaque type support. */ + case EcsOpOpaque: { + const EcsOpaque *type_ptr = ecs_get(world, op->type, EcsOpaque); + ecs_entity_t as_type = type_ptr->as_type; + const EcsMetaType *mtype_ptr = ecs_get(world, as_type, EcsMetaType); + + /* Check what kind of type the opaque type represents */ + switch(mtype_ptr->kind) { + + /* Opaque vector support */ + case EcsVectorType: { + const EcsVector *vt = ecs_get(world, type_ptr->as_type, EcsVector); + next_scope->type = vt->type; + + /* Push the element type of the vector type */ + if (flecs_meta_cursor_push_type( + world, next_scope, vt->type, NULL) != 0) + { + goto error; + } + + /* This tracks whether any data was assigned inside the scope. When + * the scope is popped, and is_empty_scope is still true, the vector + * will be resized to 0. */ + next_scope->is_empty_scope = true; + next_scope->is_collection = true; + break; + } + + /* Opaque array support */ + case EcsArrayType: { + const EcsArray *at = ecs_get(world, type_ptr->as_type, EcsArray); + next_scope->type = at->type; + + /* Push the element type of the array type */ + if (flecs_meta_cursor_push_type( + world, next_scope, at->type, NULL) != 0) + { + goto error; + } + + /* Arrays are always a fixed size */ + next_scope->is_empty_scope = false; + next_scope->is_collection = true; + break; + } + + /* Opaque struct support */ + case EcsStructType: + /* Push struct type that represents the opaque type. This ensures + * that the deserializer retains information about members and + * member types, which is necessary for nested opaque types, and + * allows for error checking. */ + if (flecs_meta_cursor_push_type( + world, next_scope, as_type, NULL) != 0) + { + goto error; + } + + /* Strip push op, since we already pushed */ + next_scope->members = next_scope->ops[0].members; + next_scope->ops = &next_scope->ops[1]; + next_scope->op_count --; + break; + + case EcsPrimitiveType: + case EcsEnumType: + case EcsBitmaskType: + case EcsOpaqueType: + default: + break; + } + + next_scope->ptr = ptr; + next_scope->opaque = type_ptr; + break; + } + + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpEntity: + case EcsOpId: { + char *path = ecs_get_fullpath(world, scope->type); + ecs_err("invalid push for type '%s'", path); + ecs_os_free(path); + goto error; + } + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + if (scope->is_collection && !scope->opaque) { + next_scope->ptr = ECS_OFFSET(next_scope->ptr, + scope->elem_cur * get_size(world, scope)); + } + + return 0; +error: + return -1; +} + +int ecs_meta_pop( + ecs_meta_cursor_t *cursor) +{ + if (cursor->is_primitive_scope) { + cursor->is_primitive_scope = false; + return 0; + } + + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + scope = flecs_meta_cursor_restore_scope(cursor, scope); + cursor->depth --; + if (cursor->depth < 0) { + ecs_err("unexpected end of scope"); + return -1; + } + + ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(next_scope); + + if (!scope->is_inline_array) { + if (op->kind == EcsOpPush) { + next_scope->op_cur += op->op_count - 1; + + /* push + op_count should point to the operation after pop */ + op = flecs_meta_cursor_get_op(next_scope); + ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); + } else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { + /* Collection type, nothing else to do */ + } else if (op->kind == EcsOpOpaque) { + const EcsOpaque *opaque = scope->opaque; + if (scope->is_collection) { + const EcsMetaType *mtype = ecs_get(cursor->world, + opaque->as_type, EcsMetaType); + ecs_assert(mtype != NULL, ECS_INTERNAL_ERROR, NULL); + + /* When popping a opaque collection type, call resize to make + * sure the vector isn't larger than the number of elements we + * deserialized. + * If the opaque type represents an array, don't call resize. */ + if (mtype->kind != EcsArrayType) { + ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (!opaque->resize) { + char *str = ecs_get_fullpath(cursor->world, scope->type); + ecs_err("missing resize for opaque type %s", str); + ecs_os_free(str); + return -1; + } + if (scope->is_empty_scope) { + /* If no values were serialized for scope, resize + * collection to 0 elements. */ + ecs_assert(!scope->elem_cur, ECS_INTERNAL_ERROR, NULL); + opaque->resize(scope->ptr, 0); + } else { + /* Otherwise resize collection to the index of the last + * deserialized element + 1 */ + opaque->resize(scope->ptr, + flecs_ito(size_t, scope->elem_cur + 1)); + } + } + } else { + /* Opaque struct type, nothing to be done */ + } + } else { + /* should not have been able to push if the previous scope was not + * a complex or collection type */ + ecs_assert(false, ECS_INTERNAL_ERROR, NULL); + } + } + + return 0; +} + +bool ecs_meta_is_collection( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + return scope->is_collection; +} + +ecs_entity_t ecs_meta_get_type( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + return op->type; +} + +ecs_entity_t ecs_meta_get_unit( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_entity_t type = scope->type; + const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); + if (!st) { + return 0; + } + + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + + return m->unit; +} + +const char* ecs_meta_get_member( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + return op->name; +} + +ecs_entity_t ecs_meta_get_member_id( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_entity_t type = scope->type; + const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); + if (!st) { + return 0; + } + + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + + return m->member; +} + +/* Utilities for type conversions and bounds checking */ +static struct { + int64_t min, max; +} ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {INT8_MIN, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, INT64_MAX}, + [EcsOpI8] = {INT8_MIN, INT8_MAX}, + [EcsOpI16] = {INT16_MIN, INT16_MAX}, + [EcsOpI32] = {INT32_MIN, INT32_MAX}, + [EcsOpI64] = {INT64_MIN, INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, + [EcsOpIPtr] = { + ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), + ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) + }, + [EcsOpEntity] = {0, INT64_MAX}, + [EcsOpId] = {0, INT64_MAX}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, INT32_MAX} +}; + +static struct { + uint64_t min, max; +} ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {0, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, UINT64_MAX}, + [EcsOpI8] = {0, INT8_MAX}, + [EcsOpI16] = {0, INT16_MAX}, + [EcsOpI32] = {0, INT32_MAX}, + [EcsOpI64] = {0, INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, + [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, + [EcsOpEntity] = {0, UINT64_MAX}, + [EcsOpId] = {0, UINT64_MAX}, + [EcsOpEnum] = {0, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; + +static struct { + double min, max; +} ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {INT8_MIN, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, (double)UINT64_MAX}, + [EcsOpI8] = {INT8_MIN, INT8_MAX}, + [EcsOpI16] = {INT16_MIN, INT16_MAX}, + [EcsOpI32] = {INT32_MIN, INT32_MAX}, + [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, + [EcsOpIPtr] = { + ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), + ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) + }, + [EcsOpEntity] = {0, (double)UINT64_MAX}, + [EcsOpId] = {0, (double)UINT64_MAX}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; + +#define set_T(T, ptr, value)\ + ((T*)ptr)[0] = ((T)value) + +#define case_T(kind, T, dst, src)\ +case kind:\ + set_T(T, dst, src);\ + break + +#define case_T_checked(kind, T, dst, src, bounds)\ +case kind:\ + if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ + ecs_err("value %.0f is out of bounds for type %s", (double)src,\ + flecs_meta_op_kind_str(kind));\ + return -1;\ + }\ + set_T(T, dst, src);\ + break + +#define cases_T_float(dst, src)\ + case_T(EcsOpF32, ecs_f32_t, dst, src);\ + case_T(EcsOpF64, ecs_f64_t, dst, src) + +#define cases_T_signed(dst, src, bounds)\ + case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ + case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ + case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ + case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ + case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ + case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ + case_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) + +#define cases_T_unsigned(dst, src, bounds)\ + case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ + case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ + case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ + case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ + case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ + case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpId, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) + +#define cases_T_bool(dst, src)\ +case EcsOpBool:\ + set_T(ecs_bool_t, dst, value != 0);\ + break + +static +void flecs_meta_conversion_error( + ecs_meta_cursor_t *cursor, + ecs_meta_type_op_t *op, + const char *from) +{ + if (op->kind == EcsOpPop) { + ecs_err("cursor: out of bounds"); + } else { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("unsupported conversion from %s to '%s'", from, path); + ecs_os_free(path); + } +} + +int ecs_meta_set_bool( + ecs_meta_cursor_t *cursor, + bool value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + case EcsOpOpaque: { + const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); + if (ot && ot->assign_bool) { + ot->assign_bool(ptr, value); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpF32: + case EcsOpF64: + case EcsOpString: + flecs_meta_conversion_error(cursor, op, "bool"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_char( + ecs_meta_cursor_t *cursor, + char value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + case EcsOpOpaque: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); + if (opaque->assign_char) { /* preferred operation */ + opaque->assign_char(ptr, value); + break; + } else if (opaque->assign_uint) { + opaque->assign_uint(ptr, (uint64_t)value); + break; + } else if (opaque->assign_int) { + opaque->assign_int(ptr, value); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpString: + case EcsOpEntity: + case EcsOpId: + flecs_meta_conversion_error(cursor, op, "char"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_int( + ecs_meta_cursor_t *cursor, + int64_t value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); + cases_T_float(ptr, value); + case EcsOpOpaque: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); + if (opaque->assign_int) { /* preferred operation */ + opaque->assign_int(ptr, value); + break; + } else if (opaque->assign_float) { /* most expressive */ + opaque->assign_float(ptr, (double)value); + break; + } else if (opaque->assign_uint && (value > 0)) { + opaque->assign_uint(ptr, flecs_ito(uint64_t, value)); + break; + } else if (opaque->assign_char && (value > 0) && (value < 256)) { + opaque->assign_char(ptr, flecs_ito(char, value)); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpString: { + if(!value) return ecs_meta_set_null(cursor); + flecs_meta_conversion_error(cursor, op, "int"); + return -1; + } + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_uint( + ecs_meta_cursor_t *cursor, + uint64_t value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); + cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + cases_T_float(ptr, value); + case EcsOpOpaque: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); + if (opaque->assign_uint) { /* preferred operation */ + opaque->assign_uint(ptr, value); + break; + } else if (opaque->assign_float) { /* most expressive */ + opaque->assign_float(ptr, (double)value); + break; + } else if (opaque->assign_int && (value < INT64_MAX)) { + opaque->assign_int(ptr, flecs_uto(int64_t, value)); + break; + } else if (opaque->assign_char && (value < 256)) { + opaque->assign_char(ptr, flecs_uto(char, value)); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpString: + if(!value) return ecs_meta_set_null(cursor); + flecs_meta_conversion_error(cursor, op, "uint"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_float( + ecs_meta_cursor_t *cursor, + double value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + case EcsOpBool: + if (ECS_EQZERO(value)) { + set_T(bool, ptr, false); + } else { + set_T(bool, ptr, true); + } + break; + cases_T_signed(ptr, value, ecs_meta_bounds_float); + cases_T_unsigned(ptr, value, ecs_meta_bounds_float); + cases_T_float(ptr, value); + case EcsOpOpaque: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); + if (opaque->assign_float) { /* preferred operation */ + opaque->assign_float(ptr, value); + break; + } else if (opaque->assign_int && /* most expressive */ + (value <= (double)INT64_MAX) && (value >= (double)INT64_MIN)) + { + opaque->assign_int(ptr, (int64_t)value); + break; + } else if (opaque->assign_uint && (value >= 0)) { + opaque->assign_uint(ptr, (uint64_t)value); + break; + } else if (opaque->assign_entity && (value >= 0)) { + opaque->assign_entity( + ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), + (ecs_entity_t)value); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpString: + flecs_meta_conversion_error(cursor, op, "float"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_value( + ecs_meta_cursor_t *cursor, + const ecs_value_t *value) +{ + ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t type = value->type; + ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); + const EcsMetaType *mt = ecs_get(cursor->world, type, EcsMetaType); + if (!mt) { + ecs_err("type of value does not have reflection data"); + return -1; + } + + if (mt->kind == EcsPrimitiveType) { + const EcsPrimitive *prim = ecs_get(cursor->world, type, EcsPrimitive); + ecs_check(prim != NULL, ECS_INTERNAL_ERROR, NULL); + switch(prim->kind) { + case EcsBool: return ecs_meta_set_bool(cursor, *(bool*)value->ptr); + case EcsChar: return ecs_meta_set_char(cursor, *(char*)value->ptr); + case EcsByte: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); + case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); + case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); + case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); + case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); + case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); + case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); + case EcsI32: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); + case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); + case EcsF32: return ecs_meta_set_float(cursor, (double)*(float*)value->ptr); + case EcsF64: return ecs_meta_set_float(cursor, *(double*)value->ptr); + case EcsUPtr: return ecs_meta_set_uint(cursor, *(uintptr_t*)value->ptr); + case EcsIPtr: return ecs_meta_set_int(cursor, *(intptr_t*)value->ptr); + case EcsString: return ecs_meta_set_string(cursor, *(char**)value->ptr); + case EcsEntity: return ecs_meta_set_entity(cursor, *(ecs_entity_t*)value->ptr); + case EcsId: return ecs_meta_set_id(cursor, *(ecs_id_t*)value->ptr); + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); + goto error; + } + } else if (mt->kind == EcsEnumType) { + return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); + } else if (mt->kind == EcsBitmaskType) { + return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); + } else { + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + if (op->type != value->type) { + char *type_str = ecs_get_fullpath(cursor->world, value->type); + flecs_meta_conversion_error(cursor, op, type_str); + ecs_os_free(type_str); + goto error; + } + return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); + } + +error: + return -1; +} + +static +int flecs_meta_add_bitmask_constant( + ecs_meta_cursor_t *cursor, + ecs_meta_type_op_t *op, + void *out, + const char *value) +{ + ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_os_strcmp(value, "0")) { + return 0; + } + + ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); + if (!c) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); + ecs_os_free(path); + return -1; + } + + const ecs_u32_t *v = ecs_get_pair_object( + cursor->world, c, EcsConstant, ecs_u32_t); + if (v == NULL) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); + ecs_os_free(path); + return -1; + } + + *(ecs_u32_t*)out |= v[0]; + + return 0; +} + +static +int flecs_meta_parse_bitmask( + ecs_meta_cursor_t *cursor, + ecs_meta_type_op_t *op, + void *out, + const char *value) +{ + char token[ECS_MAX_TOKEN_SIZE]; + + const char *prev = value, *ptr = value; + + *(ecs_u32_t*)out = 0; + + while ((ptr = strchr(ptr, '|'))) { + ecs_os_memcpy(token, prev, ptr - prev); + token[ptr - prev] = '\0'; + if (flecs_meta_add_bitmask_constant(cursor, op, out, token) != 0) { + return -1; + } + + ptr ++; + prev = ptr; + } + + if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { + return -1; + } + + return 0; +} + +static +int flecs_meta_cursor_lookup( + ecs_meta_cursor_t *cursor, + const char *value, + ecs_entity_t *out) +{ + if (ecs_os_strcmp(value, "0")) { + if (cursor->lookup_action) { + *out = cursor->lookup_action( + cursor->world, value, + cursor->lookup_ctx); + } else { + *out = ecs_lookup_path(cursor->world, 0, value); + } + if (!*out) { + ecs_err("unresolved entity identifier '%s'", value); + return -1; + } + } + return 0; +} + +int ecs_meta_set_string( + ecs_meta_cursor_t *cursor, + const char *value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + case EcsOpBool: + if (!ecs_os_strcmp(value, "true")) { + set_T(ecs_bool_t, ptr, true); + } else if (!ecs_os_strcmp(value, "false")) { + set_T(ecs_bool_t, ptr, false); + } else if (isdigit(value[0])) { + if (!ecs_os_strcmp(value, "0")) { + set_T(ecs_bool_t, ptr, false); + } else { + set_T(ecs_bool_t, ptr, true); + } + } else { + ecs_err("invalid value for boolean '%s'", value); + goto error; + } + break; + case EcsOpI8: + case EcsOpU8: + case EcsOpByte: + set_T(ecs_i8_t, ptr, atol(value)); + break; + case EcsOpChar: + set_T(char, ptr, value[0]); + break; + case EcsOpI16: + case EcsOpU16: + set_T(ecs_i16_t, ptr, atol(value)); + break; + case EcsOpI32: + case EcsOpU32: + set_T(ecs_i32_t, ptr, atol(value)); + break; + case EcsOpI64: + case EcsOpU64: + set_T(ecs_i64_t, ptr, atoll(value)); + break; + case EcsOpIPtr: + case EcsOpUPtr: + set_T(ecs_iptr_t, ptr, atoll(value)); + break; + case EcsOpF32: + set_T(ecs_f32_t, ptr, atof(value)); + break; + case EcsOpF64: + set_T(ecs_f64_t, ptr, atof(value)); + break; + case EcsOpString: { + ecs_assert(*(ecs_string_t*)ptr != value, ECS_INVALID_PARAMETER, NULL); + ecs_os_free(*(ecs_string_t*)ptr); + char *result = ecs_os_strdup(value); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpEnum: { + ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); + if (!c) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("unresolved enum constant '%s' for type '%s'", value, path); + ecs_os_free(path); + goto error; + } + + const ecs_i32_t *v = ecs_get_pair_object( + cursor->world, c, EcsConstant, ecs_i32_t); + if (v == NULL) { + char *path = ecs_get_fullpath(cursor->world, op->type); + ecs_err("'%s' is not an enum constant for type '%s'", value, path); + ecs_os_free(path); + goto error; + } + + set_T(ecs_i32_t, ptr, v[0]); + break; + } + case EcsOpBitmask: + if (flecs_meta_parse_bitmask(cursor, op, ptr, value) != 0) { + goto error; + } + break; + case EcsOpEntity: { + ecs_entity_t e = 0; + if (flecs_meta_cursor_lookup(cursor, value, &e)) { + goto error; + } + set_T(ecs_entity_t, ptr, e); + break; + } + case EcsOpId: { + ecs_id_t id = 0; +#ifdef FLECS_PARSER + ecs_term_t term = {0}; + if (ecs_parse_term( + cursor->world, NULL, value, value, &term, NULL, NULL, false)) + { + if (ecs_term_finalize(cursor->world, &term)) { + ecs_term_fini(&term); + goto error; + } + id = term.id; + ecs_term_fini(&term); + } else { + ecs_term_fini(&term); + goto error; + } +#endif + set_T(ecs_id_t, ptr, id); + + break; + } + case EcsOpPop: + ecs_err("excess element '%s' in scope", value); + goto error; + case EcsOpOpaque: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); + if (opaque->assign_string) { /* preferred */ + opaque->assign_string(ptr, value); + break; + } else if (opaque->assign_char && value[0] && !value[1]) { + opaque->assign_char(ptr, value[0]); + break; + } else if (opaque->assign_entity) { + ecs_entity_t e = 0; + if (flecs_meta_cursor_lookup(cursor, value, &e)) { + goto error; + } + opaque->assign_entity(ptr, + ECS_CONST_CAST(ecs_world_t*, cursor->world), e); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpScope: + case EcsOpPrimitive: + ecs_err("unsupported conversion from string '%s' to '%s'", + value, flecs_meta_op_kind_str(op->kind)); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_string_literal( + ecs_meta_cursor_t *cursor, + const char *value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + ecs_size_t len = ecs_os_strlen(value); + if (value[0] != '\"' || value[len - 1] != '\"') { + ecs_err("invalid string literal '%s'", value); + goto error; + } + + switch(op->kind) { + case EcsOpChar: + set_T(ecs_char_t, ptr, value[1]); + break; + + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpEntity: + case EcsOpId: + len -= 2; + + char *result = ecs_os_malloc(len + 1); + ecs_os_memcpy(result, value + 1, len); + result[len] = '\0'; + + if (ecs_meta_set_string(cursor, result)) { + ecs_os_free(result); + return -1; + } + + ecs_os_free(result); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_entity( + ecs_meta_cursor_t *cursor, + ecs_entity_t value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + case EcsOpEntity: + set_T(ecs_entity_t, ptr, value); + break; + case EcsOpId: + set_T(ecs_id_t, ptr, value); /* entities are valid ids */ + break; + case EcsOpOpaque: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + if (opaque && opaque->assign_entity) { + opaque->assign_entity(ptr, + ECS_CONST_CAST(ecs_world_t*, cursor->world), value); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + flecs_meta_conversion_error(cursor, op, "entity"); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_id( + ecs_meta_cursor_t *cursor, + ecs_entity_t value) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + + switch(op->kind) { + case EcsOpId: + set_T(ecs_id_t, ptr, value); + break; + case EcsOpOpaque: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + if (opaque && opaque->assign_id) { + opaque->assign_id(ptr, + ECS_CONST_CAST(ecs_world_t*, cursor->world), value); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpEntity: + flecs_meta_conversion_error(cursor, op, "id"); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_null( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch (op->kind) { + case EcsOpString: + ecs_os_free(*(char**)ptr); + set_T(ecs_string_t, ptr, NULL); + break; + case EcsOpOpaque: { + const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); + if (ot && ot->assign_null) { + ot->assign_null(ptr); + break; + } + } + /* fall through */ + case EcsOpArray: + case EcsOpVector: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + flecs_meta_conversion_error(cursor, op, "null"); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +bool ecs_meta_get_bool( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return *(ecs_i8_t*)ptr != 0; + case EcsOpU8: return *(ecs_u8_t*)ptr != 0; + case EcsOpChar: return *(ecs_char_t*)ptr != 0; + case EcsOpByte: return *(ecs_u8_t*)ptr != 0; + case EcsOpI16: return *(ecs_i16_t*)ptr != 0; + case EcsOpU16: return *(ecs_u16_t*)ptr != 0; + case EcsOpI32: return *(ecs_i32_t*)ptr != 0; + case EcsOpU32: return *(ecs_u32_t*)ptr != 0; + case EcsOpI64: return *(ecs_i64_t*)ptr != 0; + case EcsOpU64: return *(ecs_u64_t*)ptr != 0; + case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; + case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; + case EcsOpF32: return ECS_NEQZERO(*(ecs_f32_t*)ptr); + case EcsOpF64: return ECS_NEQZERO(*(ecs_f64_t*)ptr); + case EcsOpString: return *(const char**)ptr != NULL; + case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; + case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; + case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; + case EcsOpId: return *(ecs_id_t*)ptr != 0; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + +error: + return 0; +} + +char ecs_meta_get_char( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpChar: + return *(ecs_char_t*)ptr != 0; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpEntity: + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); + break; + } + +error: + return 0; +} + +int64_t ecs_meta_get_int( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpBool: return *(const ecs_bool_t*)ptr; + case EcsOpI8: return *(const ecs_i8_t*)ptr; + case EcsOpU8: return *(const ecs_u8_t*)ptr; + case EcsOpChar: return *(const ecs_char_t*)ptr; + case EcsOpByte: return *(const ecs_u8_t*)ptr; + case EcsOpI16: return *(const ecs_i16_t*)ptr; + case EcsOpU16: return *(const ecs_u16_t*)ptr; + case EcsOpI32: return *(const ecs_i32_t*)ptr; + case EcsOpU32: return *(const ecs_u32_t*)ptr; + case EcsOpI64: return *(const ecs_i64_t*)ptr; + case EcsOpU64: return flecs_uto(int64_t, *(const ecs_u64_t*)ptr); + case EcsOpIPtr: return *(const ecs_iptr_t*)ptr; + case EcsOpUPtr: return flecs_uto(int64_t, *(const ecs_uptr_t*)ptr); + case EcsOpF32: return (int64_t)*(const ecs_f32_t*)ptr; + case EcsOpF64: return (int64_t)*(const ecs_f64_t*)ptr; + case EcsOpString: return atoi(*(const char**)ptr); + case EcsOpEnum: return *(const ecs_i32_t*)ptr; + case EcsOpBitmask: return *(const ecs_u32_t*)ptr; + case EcsOpEntity: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from entity to int"); + break; + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from id to int"); + break; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } +error: + return 0; +} + +uint64_t ecs_meta_get_uint( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return flecs_ito(uint64_t, *(const ecs_i8_t*)ptr); + case EcsOpU8: return *(ecs_u8_t*)ptr; + case EcsOpChar: return flecs_ito(uint64_t, *(const ecs_char_t*)ptr); + case EcsOpByte: return flecs_ito(uint64_t, *(const ecs_u8_t*)ptr); + case EcsOpI16: return flecs_ito(uint64_t, *(const ecs_i16_t*)ptr); + case EcsOpU16: return *(ecs_u16_t*)ptr; + case EcsOpI32: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); + case EcsOpU32: return *(ecs_u32_t*)ptr; + case EcsOpI64: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); + case EcsOpU64: return *(ecs_u64_t*)ptr; + case EcsOpIPtr: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); + case EcsOpUPtr: return *(ecs_uptr_t*)ptr; + case EcsOpF32: return flecs_ito(uint64_t, *(const ecs_f32_t*)ptr); + case EcsOpF64: return flecs_ito(uint64_t, *(const ecs_f64_t*)ptr); + case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); + case EcsOpEnum: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); + case EcsOpBitmask: return *(const ecs_u32_t*)ptr; + case EcsOpEntity: return *(const ecs_entity_t*)ptr; + case EcsOpId: return *(const ecs_id_t*)ptr; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } +error: + return 0; +} + +static +double flecs_meta_to_float( + ecs_meta_type_op_kind_t kind, + const void *ptr) +{ + switch(kind) { + case EcsOpBool: return *(const ecs_bool_t*)ptr; + case EcsOpI8: return *(const ecs_i8_t*)ptr; + case EcsOpU8: return *(const ecs_u8_t*)ptr; + case EcsOpChar: return *(const ecs_char_t*)ptr; + case EcsOpByte: return *(const ecs_u8_t*)ptr; + case EcsOpI16: return *(const ecs_i16_t*)ptr; + case EcsOpU16: return *(const ecs_u16_t*)ptr; + case EcsOpI32: return *(const ecs_i32_t*)ptr; + case EcsOpU32: return *(const ecs_u32_t*)ptr; + case EcsOpI64: return (double)*(const ecs_i64_t*)ptr; + case EcsOpU64: return (double)*(const ecs_u64_t*)ptr; + case EcsOpIPtr: return (double)*(const ecs_iptr_t*)ptr; + case EcsOpUPtr: return (double)*(const ecs_uptr_t*)ptr; + case EcsOpF32: return (double)*(const ecs_f32_t*)ptr; + case EcsOpF64: return *(const ecs_f64_t*)ptr; + case EcsOpString: return atof(*ECS_CONST_CAST(const char**, ptr)); + case EcsOpEnum: return *(const ecs_i32_t*)ptr; + case EcsOpBitmask: return *(const ecs_u32_t*)ptr; + case EcsOpEntity: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from entity to float"); + break; + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from id to float"); + break; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } +error: + return 0; +} + +double ecs_meta_get_float( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + return flecs_meta_to_float(op->kind, ptr); +} + +const char* ecs_meta_get_string( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpString: return *(const char**)ptr; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpChar: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); + break; + } +error: + return 0; +} + +ecs_entity_t ecs_meta_get_entity( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpEntity: return *(ecs_entity_t*)ptr; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpChar: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); + break; + } +error: + return 0; +} + +ecs_entity_t ecs_meta_get_id( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); + switch(op->kind) { + case EcsOpEntity: return *(ecs_id_t*)ptr; /* Entities are valid ids */ + case EcsOpId: return *(ecs_id_t*)ptr; + case EcsOpArray: + case EcsOpVector: + case EcsOpOpaque: + case EcsOpPush: + case EcsOpPop: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpChar: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); + break; + } +error: + return 0; +} + +double ecs_meta_ptr_to_float( + ecs_primitive_kind_t type_kind, + const void *ptr) +{ + ecs_meta_type_op_kind_t kind = flecs_meta_primitive_to_op_kind(type_kind); + return flecs_meta_to_float(kind, ptr); +} + +#endif + +/** + * @file meta/meta.c + * @brief Meta addon. + */ + + +#ifdef FLECS_META + +/* ecs_string_t lifecycle */ + +static ECS_COPY(ecs_string_t, dst, src, { + ecs_os_free(*(ecs_string_t*)dst); + *(ecs_string_t*)dst = ecs_os_strdup(*(const ecs_string_t*)src); +}) + +static ECS_MOVE(ecs_string_t, dst, src, { + ecs_os_free(*(ecs_string_t*)dst); + *(ecs_string_t*)dst = *(ecs_string_t*)src; + *(ecs_string_t*)src = NULL; +}) + +static ECS_DTOR(ecs_string_t, ptr, { + ecs_os_free(*(ecs_string_t*)ptr); + *(ecs_string_t*)ptr = NULL; +}) + + +/* EcsMetaTypeSerialized lifecycle */ + +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr) +{ + int32_t i, count = ecs_vec_count(&ptr->ops); + ecs_meta_type_op_t *ops = ecs_vec_first(&ptr->ops); + + for (i = 0; i < count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; + if (op->members) { + flecs_name_index_free(op->members); + } + } + + ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_type_op_t); +} + +static ECS_COPY(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); + + dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_type_op_t); + + int32_t o, count = ecs_vec_count(&dst->ops); + ecs_meta_type_op_t *ops = ecs_vec_first_t(&dst->ops, ecs_meta_type_op_t); + + for (o = 0; o < count; o ++) { + ecs_meta_type_op_t *op = &ops[o]; + if (op->members) { + op->members = flecs_name_index_copy(op->members); + } + } +}) + +static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); + dst->ops = src->ops; + src->ops = (ecs_vec_t){0}; +}) + +static ECS_DTOR(EcsMetaTypeSerialized, ptr, { + ecs_meta_dtor_serialized(ptr); +}) + + +/* EcsStruct lifecycle */ + +static void flecs_struct_dtor( + EcsStruct *ptr) +{ + ecs_member_t *members = ecs_vec_first_t(&ptr->members, ecs_member_t); + int32_t i, count = ecs_vec_count(&ptr->members); + for (i = 0; i < count; i ++) { + ecs_os_free(ECS_CONST_CAST(char*, members[i].name)); + } + ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); +} + +static ECS_COPY(EcsStruct, dst, src, { + flecs_struct_dtor(dst); + + dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); + + ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); + int32_t m, count = ecs_vec_count(&dst->members); + + for (m = 0; m < count; m ++) { + members[m].name = ecs_os_strdup(members[m].name); + } +}) + +static ECS_MOVE(EcsStruct, dst, src, { + flecs_struct_dtor(dst); + dst->members = src->members; + src->members = (ecs_vec_t){0}; +}) + +static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) + + +/* EcsEnum lifecycle */ + +static void flecs_constants_dtor( + ecs_map_t *constants) +{ + ecs_map_iter_t it = ecs_map_iter(constants); + while (ecs_map_next(&it)) { + ecs_enum_constant_t *c = ecs_map_ptr(&it); + ecs_os_free(ECS_CONST_CAST(char*, c->name)); + ecs_os_free(c); + } + ecs_map_fini(constants); +} + +static void flecs_constants_copy( + ecs_map_t *dst, + const ecs_map_t *src) +{ + ecs_map_copy(dst, src); + + ecs_map_iter_t it = ecs_map_iter(dst); + while (ecs_map_next(&it)) { + ecs_enum_constant_t **r = ecs_map_ref(&it, ecs_enum_constant_t); + ecs_enum_constant_t *src_c = r[0]; + ecs_enum_constant_t *dst_c = ecs_os_calloc_t(ecs_enum_constant_t); + *dst_c = *src_c; + dst_c->name = ecs_os_strdup(dst_c->name); + r[0] = dst_c; + } +} + +static ECS_COPY(EcsEnum, dst, src, { + flecs_constants_dtor(&dst->constants); + flecs_constants_copy(&dst->constants, &src->constants); +}) + +static ECS_MOVE(EcsEnum, dst, src, { + flecs_constants_dtor(&dst->constants); + dst->constants = src->constants; + ecs_os_zeromem(&src->constants); +}) + +static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) + + +/* EcsBitmask lifecycle */ + +static ECS_COPY(EcsBitmask, dst, src, { + /* bitmask constant & enum constant have the same layout */ + flecs_constants_dtor(&dst->constants); + flecs_constants_copy(&dst->constants, &src->constants); +}) + +static ECS_MOVE(EcsBitmask, dst, src, { + flecs_constants_dtor(&dst->constants); + dst->constants = src->constants; + ecs_os_zeromem(&src->constants); +}) + +static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) + + +/* EcsUnit lifecycle */ + +static void dtor_unit( + EcsUnit *ptr) +{ + ecs_os_free(ptr->symbol); +} + +static ECS_COPY(EcsUnit, dst, src, { + dtor_unit(dst); + dst->symbol = ecs_os_strdup(src->symbol); + dst->base = src->base; + dst->over = src->over; + dst->prefix = src->prefix; + dst->translation = src->translation; +}) + +static ECS_MOVE(EcsUnit, dst, src, { + dtor_unit(dst); + dst->symbol = src->symbol; + dst->base = src->base; + dst->over = src->over; + dst->prefix = src->prefix; + dst->translation = src->translation; + + src->symbol = NULL; + src->base = 0; + src->over = 0; + src->prefix = 0; + src->translation = (ecs_unit_translation_t){0}; +}) + +static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) + + +/* EcsUnitPrefix lifecycle */ + +static void dtor_unit_prefix( + EcsUnitPrefix *ptr) +{ + ecs_os_free(ptr->symbol); +} + +static ECS_COPY(EcsUnitPrefix, dst, src, { + dtor_unit_prefix(dst); + dst->symbol = ecs_os_strdup(src->symbol); + dst->translation = src->translation; +}) + +static ECS_MOVE(EcsUnitPrefix, dst, src, { + dtor_unit_prefix(dst); + dst->symbol = src->symbol; + dst->translation = src->translation; + + src->symbol = NULL; + src->translation = (ecs_unit_translation_t){0}; +}) + +static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) + +/* Type initialization */ + +static +const char* flecs_type_kind_str( + ecs_type_kind_t kind) +{ + switch(kind) { + case EcsPrimitiveType: return "Primitive"; + case EcsBitmaskType: return "Bitmask"; + case EcsEnumType: return "Enum"; + case EcsStructType: return "Struct"; + case EcsArrayType: return "Array"; + case EcsVectorType: return "Vector"; + case EcsOpaqueType: return "Opaque"; + default: return "unknown"; + } +} + +static +int flecs_init_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_type_kind_t kind, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + + EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType); + if (meta_type->kind == 0) { + meta_type->existing = ecs_has(world, type, EcsComponent); + + /* Ensure that component has a default constructor, to prevent crashing + * serializers on uninitialized values. */ + ecs_type_info_t *ti = flecs_type_info_ensure(world, type); + if (!ti->hooks.ctor) { + ti->hooks.ctor = ecs_default_ctor; + } + } else { + if (meta_type->kind != kind) { + ecs_err("type '%s' reregistered as '%s' (was '%s')", + ecs_get_name(world, type), + flecs_type_kind_str(kind), + flecs_type_kind_str(meta_type->kind)); + return -1; + } + } + + if (!meta_type->existing) { + EcsComponent *comp = ecs_get_mut(world, type, EcsComponent); + comp->size = size; + comp->alignment = alignment; + ecs_modified(world, type, EcsComponent); + } else { + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (comp->size < size) { + ecs_err("computed size (%d) for '%s' is larger than actual type (%d)", + size, ecs_get_name(world, type), comp->size); + return -1; + } + if (comp->alignment < alignment) { + ecs_err("computed alignment (%d) for '%s' is larger than actual type (%d)", + alignment, ecs_get_name(world, type), comp->alignment); + return -1; + } + if (comp->size == size && comp->alignment != alignment) { + if (comp->alignment < alignment) { + ecs_err("computed size for '%s' matches with actual type but " + "alignment is different (%d vs. %d)", ecs_get_name(world, type), + alignment, comp->alignment); + } + } + + meta_type->partial = comp->size != size; + } + + meta_type->kind = kind; + ecs_modified(world, type, EcsMetaType); + + return 0; +} + +#define init_type_t(world, type, kind, T) \ + flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +static +void flecs_set_struct_member( + ecs_member_t *member, + ecs_entity_t entity, + const char *name, + ecs_entity_t type, + int32_t count, + int32_t offset, + ecs_entity_t unit, + EcsMemberRanges *ranges) +{ + member->member = entity; + member->type = type; + member->count = count; + member->unit = unit; + member->offset = offset; + + if (!count) { + member->count = 1; + } + + ecs_os_strset(ECS_CONST_CAST(char**, &member->name), name); + + if (ranges) { + member->range = ranges->value; + member->error_range = ranges->error; + member->warning_range = ranges->warning; + } else { + ecs_os_zeromem(&member->range); + ecs_os_zeromem(&member->error_range); + ecs_os_zeromem(&member->warning_range); + } +} + +static +int flecs_add_member_to_struct( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t member, + EcsMember *m, + EcsMemberRanges *ranges) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *name = ecs_get_name(world, member); + if (!name) { + char *path = ecs_get_fullpath(world, type); + ecs_err("member for struct '%s' does not have a name", path); + ecs_os_free(path); + return -1; + } + + if (!m->type) { + char *path = ecs_get_fullpath(world, member); + ecs_err("member '%s' does not have a type", path); + ecs_os_free(path); + return -1; + } + + if (ecs_get_typeid(world, m->type) == 0) { + char *path = ecs_get_fullpath(world, member); + char *ent_path = ecs_get_fullpath(world, m->type); + ecs_err("member '%s.type' is '%s' which is not a type", path, ent_path); + ecs_os_free(path); + ecs_os_free(ent_path); + return -1; + } + + ecs_entity_t unit = m->unit; + if (unit) { + if (!ecs_has(world, unit, EcsUnit)) { + ecs_err("entity '%s' for member '%s' is not a unit", + ecs_get_name(world, unit), name); + return -1; + } + + if (ecs_has(world, m->type, EcsUnit) && m->type != unit) { + ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", + ecs_get_name(world, m->type), ecs_get_name(world, unit), name); + return -1; + } + } else { + if (ecs_has(world, m->type, EcsUnit)) { + unit = m->type; + m->unit = unit; + } + } + + EcsStruct *s = ecs_get_mut(world, type, EcsStruct); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); + + /* First check if member is already added to struct */ + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); + int32_t i, count = ecs_vec_count(&s->members); + for (i = 0; i < count; i ++) { + if (members[i].member == member) { + flecs_set_struct_member(&members[i], member, name, m->type, + m->count, m->offset, unit, ranges); + break; + } + } + + /* If member wasn't added yet, add a new element to vector */ + if (i == count) { + ecs_vec_init_if_t(&s->members, ecs_member_t); + ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); + elem->name = NULL; + flecs_set_struct_member(elem, member, name, m->type, + m->count, m->offset, unit, ranges); + + /* Reobtain members array in case it was reallocated */ + members = ecs_vec_first_t(&s->members, ecs_member_t); + count ++; + } + + bool explicit_offset = false; + if (m->offset) { + explicit_offset = true; + } + + /* Compute member offsets and size & alignment of struct */ + ecs_size_t size = 0; + ecs_size_t alignment = 0; + + if (!explicit_offset) { + for (i = 0; i < count; i ++) { + ecs_member_t *elem = &members[i]; + + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); + + /* Get component of member type to get its size & alignment */ + const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); + if (!mbr_comp) { + char *path = ecs_get_fullpath(world, elem->type); + ecs_err("member '%s' is not a type", path); + ecs_os_free(path); + return -1; + } + + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; + + if (!member_size || !member_alignment) { + char *path = ecs_get_fullpath(world, elem->type); + ecs_err("member '%s' has 0 size/alignment", path); + ecs_os_free(path); + return -1; + } + + member_size *= elem->count; + size = ECS_ALIGN(size, member_alignment); + elem->size = member_size; + elem->offset = size; + + /* Synchronize offset with Member component */ + if (elem->member == member) { + m->offset = elem->offset; + } else { + EcsMember *other = ecs_get_mut(world, elem->member, EcsMember); + other->offset = elem->offset; + } + + size += member_size; + + if (member_alignment > alignment) { + alignment = member_alignment; + } + } + } else { + /* If members have explicit offsets, we can't rely on computed + * size/alignment values. Calculate size as if this is the last member + * instead, since this will validate if the member fits in the struct. + * It doesn't matter if the size is smaller than the actual struct size + * because flecs_init_type function compares computed size with actual + * (component) size to determine if the type is partial. */ + ecs_member_t *elem = &members[i]; + + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); + + /* Get component of member type to get its size & alignment */ + const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); + if (!mbr_comp) { + char *path = ecs_get_fullpath(world, elem->type); + ecs_err("member '%s' is not a type", path); + ecs_os_free(path); + return -1; + } + + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; + + if (!member_size || !member_alignment) { + char *path = ecs_get_fullpath(world, elem->type); + ecs_err("member '%s' has 0 size/alignment", path); + ecs_os_free(path); + return -1; + } + + member_size *= elem->count; + elem->size = member_size; + + size = elem->offset + member_size; + + const EcsComponent* comp = ecs_get(world, type, EcsComponent); + alignment = comp->alignment; + } + + if (size == 0) { + ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); + return -1; + } + + if (alignment == 0) { + ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); + return -1; + } + + /* Align struct size to struct alignment */ + size = ECS_ALIGN(size, alignment); + + ecs_modified(world, type, EcsStruct); + + /* Do this last as it triggers the update of EcsMetaTypeSerialized */ + if (flecs_init_type(world, type, EcsStructType, size, alignment)) { + return -1; + } + + /* If current struct is also a member, assign to itself */ + if (ecs_has(world, type, EcsMember)) { + EcsMember *type_mbr = ecs_get_mut(world, type, EcsMember); + ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); + + type_mbr->type = type; + type_mbr->count = 1; + + ecs_modified(world, type, EcsMember); + } + + return 0; +} + +static +int flecs_add_constant_to_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) +{ + EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum); + + /* Remove constant from map if it was already added */ + ecs_map_iter_t it = ecs_map_iter(&ptr->constants); + while (ecs_map_next(&it)) { + ecs_enum_constant_t *c = ecs_map_ptr(&it); + if (c->constant == e) { + ecs_os_free(ECS_CONST_CAST(char*, c->name)); + ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); + } + } + + /* Check if constant sets explicit value */ + int32_t value = 0; + bool value_set = false; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_second(world, constant_id) != ecs_id(ecs_i32_t)) { + char *path = ecs_get_fullpath(world, e); + ecs_err("expected i32 type for enum constant '%s'", path); + ecs_os_free(path); + return -1; + } + + const int32_t *value_ptr = ecs_get_pair_object( + world, e, EcsConstant, ecs_i32_t); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + value = *value_ptr; + value_set = true; + } + + /* Make sure constant value doesn't conflict if set / find the next value */ + it = ecs_map_iter(&ptr->constants); + while (ecs_map_next(&it)) { + ecs_enum_constant_t *c = ecs_map_ptr(&it); + if (value_set) { + if (c->value == value) { + char *path = ecs_get_fullpath(world, e); + ecs_err("conflicting constant value %d for '%s' (other is '%s')", + value, path, c->name); + ecs_os_free(path); + return -1; + } + } else { + if (c->value >= value) { + value = c->value + 1; + } + } + } + + ecs_map_init_if(&ptr->constants, &world->allocator); + ecs_enum_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, + ecs_enum_constant_t, (ecs_map_key_t)value); + c->name = ecs_os_strdup(ecs_get_name(world, e)); + c->value = value; + c->constant = e; + + ecs_i32_t *cptr = ecs_get_mut_pair_object( + world, e, EcsConstant, ecs_i32_t); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + cptr[0] = value; + + cptr = ecs_get_mut_id(world, e, type); + cptr[0] = value; + + return 0; +} + +static +int flecs_add_constant_to_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) +{ + EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask); + + /* Remove constant from map if it was already added */ + ecs_map_iter_t it = ecs_map_iter(&ptr->constants); + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *c = ecs_map_ptr(&it); + if (c->constant == e) { + ecs_os_free(ECS_CONST_CAST(char*, c->name)); + ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); + } + } + + /* Check if constant sets explicit value */ + uint32_t value = 1; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { + char *path = ecs_get_fullpath(world, e); + ecs_err("expected u32 type for bitmask constant '%s'", path); + ecs_os_free(path); + return -1; + } + + const uint32_t *value_ptr = ecs_get_pair_object( + world, e, EcsConstant, ecs_u32_t); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + value = *value_ptr; + } else { + value = 1u << (ecs_u32_t)ecs_map_count(&ptr->constants); + } + + /* Make sure constant value doesn't conflict */ + it = ecs_map_iter(&ptr->constants); + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *c = ecs_map_ptr(&it); + if (c->value == value) { + char *path = ecs_get_fullpath(world, e); + ecs_err("conflicting constant value for '%s' (other is '%s')", + path, c->name); + ecs_os_free(path); + return -1; + } + } + + ecs_map_init_if(&ptr->constants, &world->allocator); + + ecs_bitmask_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, + ecs_bitmask_constant_t, value); + c->name = ecs_os_strdup(ecs_get_name(world, e)); + c->value = value; + c->constant = e; + + ecs_u32_t *cptr = ecs_get_mut_pair_object( + world, e, EcsConstant, ecs_u32_t); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + cptr[0] = value; + + cptr = ecs_get_mut_id(world, e, type); + cptr[0] = value; + + return 0; +} + +static +void flecs_set_primitive(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + switch(type->kind) { + case EcsBool: + init_type_t(world, e, EcsPrimitiveType, bool); + break; + case EcsChar: + init_type_t(world, e, EcsPrimitiveType, char); + break; + case EcsByte: + init_type_t(world, e, EcsPrimitiveType, bool); + break; + case EcsU8: + init_type_t(world, e, EcsPrimitiveType, uint8_t); + break; + case EcsU16: + init_type_t(world, e, EcsPrimitiveType, uint16_t); + break; + case EcsU32: + init_type_t(world, e, EcsPrimitiveType, uint32_t); + break; + case EcsU64: + init_type_t(world, e, EcsPrimitiveType, uint64_t); + break; + case EcsI8: + init_type_t(world, e, EcsPrimitiveType, int8_t); + break; + case EcsI16: + init_type_t(world, e, EcsPrimitiveType, int16_t); + break; + case EcsI32: + init_type_t(world, e, EcsPrimitiveType, int32_t); + break; + case EcsI64: + init_type_t(world, e, EcsPrimitiveType, int64_t); + break; + case EcsF32: + init_type_t(world, e, EcsPrimitiveType, float); + break; + case EcsF64: + init_type_t(world, e, EcsPrimitiveType, double); + break; + case EcsUPtr: + init_type_t(world, e, EcsPrimitiveType, uintptr_t); + break; + case EcsIPtr: + init_type_t(world, e, EcsPrimitiveType, intptr_t); + break; + case EcsString: + init_type_t(world, e, EcsPrimitiveType, char*); + break; + case EcsEntity: + init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); + break; + case EcsId: + init_type_t(world, e, EcsPrimitiveType, ecs_id_t); + break; + } + } +} + +static +void flecs_set_member(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMember *member = ecs_field(it, EcsMember, 1); + EcsMemberRanges *ranges = ecs_table_get_id(world, it->table, + ecs_id(EcsMemberRanges), it->offset); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); + continue; + } + + flecs_add_member_to_struct(world, parent, e, &member[i], + ranges ? &ranges[i] : NULL); + } +} + +static +void flecs_set_member_ranges(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 1); + EcsMember *member = ecs_table_get_id(world, it->table, + ecs_id(EcsMember), it->offset); + if (!member) { + return; + } + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); + continue; + } + + flecs_add_member_to_struct(world, parent, e, &member[i], + &ranges[i]); + } +} + +static +void flecs_add_enum(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { + continue; + } + + ecs_add_id(world, e, EcsExclusive); + ecs_add_id(world, e, EcsOneOf); + ecs_add_id(world, e, EcsTag); + } +} + +static +void flecs_add_bitmask(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { + continue; + } + } +} + +static +void flecs_add_constant(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); + continue; + } + + if (ecs_has(world, parent, EcsEnum)) { + flecs_add_constant_to_enum(world, parent, e, it->event_id); + } else if (ecs_has(world, parent, EcsBitmask)) { + flecs_add_constant_to_bitmask(world, parent, e, it->event_id); + } + } +} + +static +void flecs_set_array(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsArray *array = ecs_field(it, EcsArray, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t elem_type = array[i].type; + int32_t elem_count = array[i].count; + + if (!elem_type) { + ecs_err("array '%s' has no element type", ecs_get_name(world, e)); + continue; + } + + if (!elem_count) { + ecs_err("array '%s' has size 0", ecs_get_name(world, e)); + continue; + } + + const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); + if (flecs_init_type(world, e, EcsArrayType, + elem_ptr->size * elem_count, elem_ptr->alignment)) + { + continue; + } + } +} + +static +void flecs_set_vector(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsVector *array = ecs_field(it, EcsVector, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t elem_type = array[i].type; + + if (!elem_type) { + ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); + continue; + } + + if (init_type_t(world, e, EcsVectorType, ecs_vec_t)) { + continue; + } + } +} + +static +void flecs_set_custom_type(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsOpaque *serialize = ecs_field(it, EcsOpaque, 1); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t elem_type = serialize[i].as_type; + + if (!elem_type) { + ecs_err("custom type '%s' has no mapping type", ecs_get_name(world, e)); + continue; + } + + const EcsComponent *comp = ecs_get(world, e, EcsComponent); + if (!comp || !comp->size || !comp->alignment) { + ecs_err("custom type '%s' has no size/alignment, register as component first", + ecs_get_name(world, e)); + continue; + } + + if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { + continue; + } + } +} + +bool flecs_unit_validate( + ecs_world_t *world, + ecs_entity_t t, + EcsUnit *data) +{ + char *derived_symbol = NULL; + const char *symbol = data->symbol; + + ecs_entity_t base = data->base; + ecs_entity_t over = data->over; + ecs_entity_t prefix = data->prefix; + ecs_unit_translation_t translation = data->translation; + + if (base) { + if (!ecs_has(world, base, EcsUnit)) { + ecs_err("entity '%s' for unit '%s' used as base is not a unit", + ecs_get_name(world, base), ecs_get_name(world, t)); + goto error; + } + } + + if (over) { + if (!base) { + ecs_err("invalid unit '%s': cannot specify over without base", + ecs_get_name(world, t)); + goto error; + } + if (!ecs_has(world, over, EcsUnit)) { + ecs_err("entity '%s' for unit '%s' used as over is not a unit", + ecs_get_name(world, over), ecs_get_name(world, t)); + goto error; + } + } + + if (prefix) { + if (!base) { + ecs_err("invalid unit '%s': cannot specify prefix without base", + ecs_get_name(world, t)); + goto error; + } + const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); + if (!prefix_ptr) { + ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", + ecs_get_name(world, over), ecs_get_name(world, t)); + goto error; + } + + if (translation.factor || translation.power) { + if (prefix_ptr->translation.factor != translation.factor || + prefix_ptr->translation.power != translation.power) + { + ecs_err( + "factor for unit '%s' is inconsistent with prefix '%s'", + ecs_get_name(world, t), ecs_get_name(world, prefix)); + goto error; + } + } else { + translation = prefix_ptr->translation; + } + } + + if (base) { + bool must_match = false; /* Must base symbol match symbol? */ + ecs_strbuf_t sbuf = ECS_STRBUF_INIT; + if (prefix) { + const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (ptr->symbol) { + ecs_strbuf_appendstr(&sbuf, ptr->symbol); + must_match = true; + } + } + + const EcsUnit *uptr = ecs_get(world, base, EcsUnit); + ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (uptr->symbol) { + ecs_strbuf_appendstr(&sbuf, uptr->symbol); + } + + if (over) { + uptr = ecs_get(world, over, EcsUnit); + ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (uptr->symbol) { + ecs_strbuf_appendch(&sbuf, '/'); + ecs_strbuf_appendstr(&sbuf, uptr->symbol); + must_match = true; + } + } + + derived_symbol = ecs_strbuf_get(&sbuf); + if (derived_symbol && !ecs_os_strlen(derived_symbol)) { + ecs_os_free(derived_symbol); + derived_symbol = NULL; + } + + if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { + if (must_match) { + ecs_err("symbol '%s' for unit '%s' does not match base" + " symbol '%s'", symbol, + ecs_get_name(world, t), derived_symbol); + goto error; + } + } + if (!symbol && derived_symbol && (prefix || over)) { + ecs_os_free(data->symbol); + data->symbol = derived_symbol; + } else { + ecs_os_free(derived_symbol); + } + } + + data->base = base; + data->over = over; + data->prefix = prefix; + data->translation = translation; + + return true; +error: + ecs_os_free(derived_symbol); + return false; +} + +static +void flecs_set_unit(ecs_iter_t *it) { + EcsUnit *u = ecs_field(it, EcsUnit, 1); + + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + flecs_unit_validate(world, e, &u[i]); + } +} + +static +void flecs_unit_quantity_monitor(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + int i, count = it->count; + if (it->event == EcsOnAdd) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_add_pair(world, e, EcsQuantity, e); + } + } else { + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_remove_pair(world, e, EcsQuantity, e); + } + } +} + +static +void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMetaType *type = ecs_field(it, EcsMetaType, 1); + + int i; + for (i = 0; i < it->count; i ++) { + /* If a component is defined from reflection data, configure it with the + * default constructor. This ensures that a new component value does not + * contain uninitialized memory, which could cause serializers to crash + * when for example inspecting string fields. */ + if (!type->existing) { + ecs_entity_t e = it->entities[i]; + const ecs_type_info_t *ti = ecs_get_type_info(world, e); + if (!ti || !ti->hooks.ctor) { + ecs_set_hooks_id(world, e, + &(ecs_type_hooks_t){ + .ctor = ecs_default_ctor + }); + } + } + } +} + +static +void flecs_member_on_set(ecs_iter_t *it) { + EcsMember *mbr = it->ptrs[0]; + if (!mbr->count) { + mbr->count = 1; + } +} + +void FlecsMetaImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsMeta); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsMetaType); + flecs_bootstrap_component(world, EcsMetaTypeSerialized); + flecs_bootstrap_component(world, EcsPrimitive); + flecs_bootstrap_component(world, EcsEnum); + flecs_bootstrap_component(world, EcsBitmask); + flecs_bootstrap_component(world, EcsMember); + flecs_bootstrap_component(world, EcsMemberRanges); + flecs_bootstrap_component(world, EcsStruct); + flecs_bootstrap_component(world, EcsArray); + flecs_bootstrap_component(world, EcsVector); + flecs_bootstrap_component(world, EcsOpaque); + flecs_bootstrap_component(world, EcsUnit); + flecs_bootstrap_component(world, EcsUnitPrefix); + + flecs_bootstrap_tag(world, EcsConstant); + flecs_bootstrap_tag(world, EcsQuantity); + + ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); + + ecs_set_hooks(world, EcsMetaTypeSerialized, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsMetaTypeSerialized), + .copy = ecs_copy(EcsMetaTypeSerialized), + .dtor = ecs_dtor(EcsMetaTypeSerialized) + }); + + ecs_set_hooks(world, EcsStruct, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsStruct), + .copy = ecs_copy(EcsStruct), + .dtor = ecs_dtor(EcsStruct) + }); + + ecs_set_hooks(world, EcsMember, { + .ctor = ecs_default_ctor, + .on_set = flecs_member_on_set + }); + + ecs_set_hooks(world, EcsMemberRanges, { + .ctor = ecs_default_ctor + }); + + ecs_set_hooks(world, EcsEnum, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsEnum), + .copy = ecs_copy(EcsEnum), + .dtor = ecs_dtor(EcsEnum) + }); + + ecs_set_hooks(world, EcsBitmask, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsBitmask), + .copy = ecs_copy(EcsBitmask), + .dtor = ecs_dtor(EcsBitmask) + }); + + ecs_set_hooks(world, EcsUnit, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsUnit), + .copy = ecs_copy(EcsUnit), + .dtor = ecs_dtor(EcsUnit) + }); + + ecs_set_hooks(world, EcsUnitPrefix, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsUnitPrefix), + .copy = ecs_copy(EcsUnitPrefix), + .dtor = ecs_dtor(EcsUnitPrefix) + }); + + /* Register triggers to finalize type information from component data */ + ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ + world, EcsFlecsInternals); + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_primitive + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_member + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMemberRanges), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_member_ranges + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_enum + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_bitmask + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_constant + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_add_constant + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_array + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_vector + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_custom_type + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_unit + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_serialized_init + }); + + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_init_default_ctor + }); + + ecs_observer(world, { + .filter.terms = { + { .id = ecs_id(EcsUnit) }, + { .id = EcsQuantity } + }, + .events = { EcsMonitor }, + .callback = flecs_unit_quantity_monitor + }); + ecs_set_scope(world, old_scope); + + /* Initialize primitive types */ + #define ECS_PRIMITIVE(world, type, primitive_kind)\ + ecs_entity_init(world, &(ecs_entity_desc_t){\ + .id = ecs_id(ecs_##type##_t),\ + .name = #type,\ + .symbol = #type });\ + ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ + .kind = primitive_kind\ + }); + + ECS_PRIMITIVE(world, bool, EcsBool); + ECS_PRIMITIVE(world, char, EcsChar); + ECS_PRIMITIVE(world, byte, EcsByte); + ECS_PRIMITIVE(world, u8, EcsU8); + ECS_PRIMITIVE(world, u16, EcsU16); + ECS_PRIMITIVE(world, u32, EcsU32); + ECS_PRIMITIVE(world, u64, EcsU64); + ECS_PRIMITIVE(world, uptr, EcsUPtr); + ECS_PRIMITIVE(world, i8, EcsI8); + ECS_PRIMITIVE(world, i16, EcsI16); + ECS_PRIMITIVE(world, i32, EcsI32); + ECS_PRIMITIVE(world, i64, EcsI64); + ECS_PRIMITIVE(world, iptr, EcsIPtr); + ECS_PRIMITIVE(world, f32, EcsF32); + ECS_PRIMITIVE(world, f64, EcsF64); + ECS_PRIMITIVE(world, string, EcsString); + ECS_PRIMITIVE(world, entity, EcsEntity); + ECS_PRIMITIVE(world, id, EcsId); + + #undef ECS_PRIMITIVE + + ecs_set_hooks(world, ecs_string_t, { + .ctor = ecs_default_ctor, + .copy = ecs_copy(ecs_string_t), + .move = ecs_move(ecs_string_t), + .dtor = ecs_dtor(ecs_string_t) + }); + + /* Set default child components */ + ecs_add_pair(world, ecs_id(EcsStruct), + EcsDefaultChildComponent, ecs_id(EcsMember)); + + ecs_add_pair(world, ecs_id(EcsMember), + EcsDefaultChildComponent, ecs_id(EcsMember)); + + ecs_add_pair(world, ecs_id(EcsEnum), + EcsDefaultChildComponent, EcsConstant); + + ecs_add_pair(world, ecs_id(EcsBitmask), + EcsDefaultChildComponent, EcsConstant); + + /* Relationship properties */ + ecs_add_id(world, EcsQuantity, EcsExclusive); + ecs_add_id(world, EcsQuantity, EcsTag); + + /* Initialize reflection data for meta components */ + ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ + .entity = ecs_entity(world, { .name = "TypeKind" }), + .constants = { + { .name = "PrimitiveType" }, + { .name = "BitmaskType" }, + { .name = "EnumType" }, + { .name = "StructType" }, + { .name = "ArrayType" }, + { .name = "VectorType" }, + { .name = "OpaqueType" } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMetaType), + .members = { + { .name = "kind", .type = type_kind } + } + }); + + ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ + .entity = ecs_entity(world, { .name = "PrimitiveKind" }), + .constants = { + { .name = "Bool", 1 }, + { .name = "Char" }, + { .name = "Byte" }, + { .name = "U8" }, + { .name = "U16" }, + { .name = "U32" }, + { .name = "U64 "}, + { .name = "I8" }, + { .name = "I16" }, + { .name = "I32" }, + { .name = "I64" }, + { .name = "F32" }, + { .name = "F64" }, + { .name = "UPtr "}, + { .name = "IPtr" }, + { .name = "String" }, + { .name = "Entity" }, + { .name = "Id" } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsPrimitive), + .members = { + { .name = "kind", .type = primitive_kind } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMember), + .members = { + { .name = "type", .type = ecs_id(ecs_entity_t) }, + { .name = "count", .type = ecs_id(ecs_i32_t) }, + { .name = "unit", .type = ecs_id(ecs_entity_t) }, + { .name = "offset", .type = ecs_id(ecs_i32_t) } + } + }); + + ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, { .name = "value_range" }), + .members = { + { .name = "min", .type = ecs_id(ecs_f64_t) }, + { .name = "max", .type = ecs_id(ecs_f64_t) } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMemberRanges), + .members = { + { .name = "value", .type = vr }, + { .name = "warning", .type = vr }, + { .name = "error", .type = vr } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsArray), + .members = { + { .name = "type", .type = ecs_id(ecs_entity_t) }, + { .name = "count", .type = ecs_id(ecs_i32_t) }, + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsVector), + .members = { + { .name = "type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsOpaque), + .members = { + { .name = "as_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, { .name = "unit_translation" }), + .members = { + { .name = "factor", .type = ecs_id(ecs_i32_t) }, + { .name = "power", .type = ecs_id(ecs_i32_t) } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsUnit), + .members = { + { .name = "symbol", .type = ecs_id(ecs_string_t) }, + { .name = "prefix", .type = ecs_id(ecs_entity_t) }, + { .name = "base", .type = ecs_id(ecs_entity_t) }, + { .name = "over", .type = ecs_id(ecs_entity_t) }, + { .name = "translation", .type = ut } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsUnitPrefix), + .members = { + { .name = "symbol", .type = ecs_id(ecs_string_t) }, + { .name = "translation", .type = ut } + } + }); +} + +#endif + +/** + * @file meta/serialized.c + * @brief Serialize type into flat operations array to speed up deserialization. + */ + + +#ifdef FLECS_META + +static +int flecs_meta_serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops); + +ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) { + return EcsOpPrimitive + kind; +} + +static +ecs_size_t flecs_meta_type_size(ecs_world_t *world, ecs_entity_t type) { + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + return comp->size; +} + +static +ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) { + ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_type_op_t); + op->kind = kind; + op->offset = 0; + op->count = 1; + op->op_count = 1; + op->size = 0; + op->name = NULL; + op->members = NULL; + op->type = 0; + op->member_index = 0; + return op; +} + +static +ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) { + ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_op_t, index); + ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); + return op; +} + +static +int flecs_meta_serialize_primitive( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); + if (!ptr) { + char *name = ecs_get_fullpath(world, type); + ecs_err("entity '%s' is not a primitive type", name); + ecs_os_free(name); + return -1; + } + + ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind)); + op->offset = offset; + op->type = type; + op->size = flecs_meta_type_size(world, type); + return 0; +} + +static +int flecs_meta_serialize_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + + ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum); + op->offset = offset; + op->type = type; + op->size = ECS_SIZEOF(ecs_i32_t); + return 0; +} + +static +int flecs_meta_serialize_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + + ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask); + op->offset = offset; + op->type = type; + op->size = ECS_SIZEOF(ecs_u32_t); + return 0; +} + +static +int flecs_meta_serialize_array( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + + ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray); + op->offset = offset; + op->type = type; + op->size = flecs_meta_type_size(world, type); + return 0; +} + +static +int flecs_meta_serialize_array_component( + ecs_world_t *world, + ecs_entity_t type, + ecs_vec_t *ops) +{ + const EcsArray *ptr = ecs_get(world, type, EcsArray); + if (!ptr) { + return -1; /* Should never happen, will trigger internal error */ + } + + if (flecs_meta_serialize_type(world, ptr->type, 0, ops) != 0) { + return -1; + } + + ecs_meta_type_op_t *first = ecs_vec_first(ops); + first->count = ptr->count; + return 0; +} + +static +int flecs_meta_serialize_vector( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector); + op->offset = offset; + op->type = type; + op->size = flecs_meta_type_size(world, type); + return 0; +} + +static +int flecs_meta_serialize_custom_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque); + op->offset = offset; + op->type = type; + op->size = flecs_meta_type_size(world, type); + return 0; +} + +static +int flecs_meta_serialize_struct( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + const EcsStruct *ptr = ecs_get(world, type, EcsStruct); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t cur, first = ecs_vec_count(ops); + ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush); + op->offset = offset; + op->type = type; + op->size = flecs_meta_type_size(world, type); + + ecs_member_t *members = ecs_vec_first(&ptr->members); + int32_t i, count = ecs_vec_count(&ptr->members); + + ecs_hashmap_t *member_index = NULL; + if (count) { + op->members = member_index = flecs_name_index_new( + world, &world->allocator); + } + + for (i = 0; i < count; i ++) { + ecs_member_t *member = &members[i]; + + cur = ecs_vec_count(ops); + if (flecs_meta_serialize_type(world, member->type, offset + member->offset, ops) != 0) { + continue; + } + + op = flecs_meta_ops_get(ops, cur); + if (!op->type) { + op->type = member->type; + } + + if (op->count <= 1) { + op->count = member->count; + } + + const char *member_name = member->name; + op->name = member_name; + op->op_count = ecs_vec_count(ops) - cur; + op->member_index = i; + + flecs_name_index_ensure( + member_index, flecs_ito(uint64_t, cur - first - 1), + member_name, 0, 0); + } + + flecs_meta_ops_add(ops, EcsOpPop); + flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first; + return 0; +} + +static +int flecs_meta_serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); + if (!ptr) { + char *path = ecs_get_fullpath(world, type); + ecs_err("missing EcsMetaType for type %s'", path); + ecs_os_free(path); + return -1; + } + + switch(ptr->kind) { + case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); + case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); + case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); + case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); + case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); + case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); + case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); + } + + return 0; +} + +static +int flecs_meta_serialize_component( + ecs_world_t *world, + ecs_entity_t type, + ecs_vec_t *ops) +{ + const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); + if (!ptr) { + char *path = ecs_get_fullpath(world, type); + ecs_err("missing EcsMetaType for type %s'", path); + ecs_os_free(path); + return -1; + } + + if (ptr->kind == EcsArrayType) { + return flecs_meta_serialize_array_component(world, type, ops); + } else { + return flecs_meta_serialize_type(world, type, 0, ops); + } +} + +void ecs_meta_type_serialized_init( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_vec_t ops; + ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0); + flecs_meta_serialize_component(world, e, &ops); + + EcsMetaTypeSerialized *ptr = ecs_get_mut( + world, e, EcsMetaTypeSerialized); + if (ptr->ops.array) { + ecs_meta_dtor_serialized(ptr); + } + + ptr->ops = ops; + } +} + +#endif + +/** + * @file addons/os_api_impl/os_api_impl.c + * @brief Builtin implementation for OS API. + */ + + +#ifdef FLECS_OS_API_IMPL +#ifdef ECS_TARGET_WINDOWS +/** + * @file addons/os_api_impl/posix_impl.inl + * @brief Builtin Windows implementation for OS API. + */ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include + +typedef struct ecs_win_thread_t { + HANDLE thread; + ecs_os_thread_callback_t callback; + void *arg; +} ecs_win_thread_t; + +static +DWORD flecs_win_thread(void *ptr) { + ecs_win_thread_t *thread = ptr; + thread->callback(thread->arg); + return 0; +} + +static +ecs_os_thread_t win_thread_new( + ecs_os_thread_callback_t callback, + void *arg) +{ + ecs_win_thread_t *thread = ecs_os_malloc_t(ecs_win_thread_t); + thread->arg= arg; + thread->callback = callback; + thread->thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)flecs_win_thread, thread, 0, NULL); + return (ecs_os_thread_t)(uintptr_t)thread; +} + +static +void* win_thread_join( + ecs_os_thread_t thr) +{ + ecs_win_thread_t *thread = (ecs_win_thread_t*)(uintptr_t)thr; + DWORD r = WaitForSingleObject(thread->thread, INFINITE); + if (r == WAIT_FAILED) { + ecs_err("win_thread_join: WaitForSingleObject failed"); + } + ecs_os_free(thread); + return NULL; +} + +static +ecs_os_thread_id_t win_thread_self(void) +{ + return (ecs_os_thread_id_t)GetCurrentThreadId(); +} + +static +int32_t win_ainc( + int32_t *count) +{ + return InterlockedIncrement((volatile long*)count); +} + +static +int32_t win_adec( + int32_t *count) +{ + return InterlockedDecrement((volatile long*)count); +} + +static +int64_t win_lainc( + int64_t *count) +{ + return InterlockedIncrement64(count); +} + +static +int64_t win_ladec( + int64_t *count) +{ + return InterlockedDecrement64(count); +} + +static +ecs_os_mutex_t win_mutex_new(void) { + CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); + InitializeCriticalSection(mutex); + return (ecs_os_mutex_t)(uintptr_t)mutex; +} + +static +void win_mutex_free( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + DeleteCriticalSection(mutex); + ecs_os_free(mutex); +} + +static +void win_mutex_lock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + EnterCriticalSection(mutex); +} + +static +void win_mutex_unlock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + LeaveCriticalSection(mutex); +} + +static +ecs_os_cond_t win_cond_new(void) { + CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); + InitializeConditionVariable(cond); + return (ecs_os_cond_t)(uintptr_t)cond; +} + +static +void win_cond_free( + ecs_os_cond_t c) +{ + (void)c; +} + +static +void win_cond_signal( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeConditionVariable(cond); +} + +static +void win_cond_broadcast( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeAllConditionVariable(cond); +} + +static +void win_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + SleepConditionVariableCS(cond, mutex, INFINITE); +} + +static bool win_time_initialized; +static double win_time_freq; +static LARGE_INTEGER win_time_start; +static ULONG win_current_resolution; + +static +void win_time_setup(void) { + if ( win_time_initialized) { + return; + } + + win_time_initialized = true; + + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&win_time_start); + win_time_freq = (double)freq.QuadPart / 1000000000.0; +} + +static +void win_sleep( + int32_t sec, + int32_t nanosec) +{ + 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); +} + +static +void win_enable_high_timer_resolution(bool enable) +{ + HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); + if (!hntdll) { + return; + } + + union { + LONG (__stdcall *f)( + ULONG desired, BOOLEAN set, ULONG * current); + FARPROC p; + } func; + + func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); + if(!func.p) { + return; + } + + ULONG current, resolution = 10000; /* 1 ms */ + + if (!enable && win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); + win_current_resolution = 0; + return; + } else if (!enable) { + return; + } + + if (resolution == win_current_resolution) { + return; + } + + if (win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); + } + + if (func.f(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(func.f(resolution, 1, ¤t)) return; + } + + win_current_resolution = resolution; +} + +static +uint64_t win_time_now(void) { + uint64_t now; + + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); + + return now; +} + +static +void win_fini(void) { + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(false); + } +} + +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = win_thread_new; + api.thread_join_ = win_thread_join; + api.thread_self_ = win_thread_self; + api.task_new_ = win_thread_new; + api.task_join_ = win_thread_join; + api.ainc_ = win_ainc; + api.adec_ = win_adec; + api.lainc_ = win_lainc; + api.ladec_ = win_ladec; + api.mutex_new_ = win_mutex_new; + api.mutex_free_ = win_mutex_free; + api.mutex_lock_ = win_mutex_lock; + api.mutex_unlock_ = win_mutex_unlock; + api.cond_new_ = win_cond_new; + api.cond_free_ = win_cond_free; + api.cond_signal_ = win_cond_signal; + api.cond_broadcast_ = win_cond_broadcast; + api.cond_wait_ = win_cond_wait; + api.sleep_ = win_sleep; + api.now_ = win_time_now; + api.fini_ = win_fini; + + win_time_setup(); + + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(true); + } + + ecs_os_set_api(&api); +} + +#else +/** + * @file addons/os_api_impl/posix_impl.inl + * @brief Builtin POSIX implementation for OS API. + */ + +#include "pthread.h" + +#if defined(__APPLE__) && defined(__MACH__) +#include +#elif defined(__EMSCRIPTEN__) +#include +#else +#include +#endif + +/* This mutex is used to emulate atomic operations when the gnu builtins are + * not supported. This is probably not very fast but if the compiler doesn't + * support the gnu built-ins, then speed is probably not a priority. */ +#ifndef __GNUC__ +static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +static +ecs_os_thread_t posix_thread_new( + ecs_os_thread_callback_t callback, + void *arg) +{ + pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); + + if (pthread_create (thread, NULL, callback, arg) != 0) { + ecs_os_abort(); + } + + return (ecs_os_thread_t)(uintptr_t)thread; +} + +static +void* posix_thread_join( + ecs_os_thread_t thread) +{ + void *arg; + pthread_t *thr = (pthread_t*)(uintptr_t)thread; + pthread_join(*thr, &arg); + ecs_os_free(thr); + return arg; +} + +static +ecs_os_thread_id_t posix_thread_self(void) +{ + return (ecs_os_thread_id_t)pthread_self(); +} + +static +int32_t posix_ainc( + int32_t *count) +{ + int value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +int32_t posix_adec( + int32_t *count) +{ + int32_t value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) -= 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +int64_t posix_lainc( + int64_t *count) +{ + int64_t value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +int64_t posix_ladec( + int64_t *count) +{ + int64_t value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) -= 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +ecs_os_mutex_t posix_mutex_new(void) { + pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); + if (pthread_mutex_init(mutex, NULL)) { + abort(); + } + return (ecs_os_mutex_t)(uintptr_t)mutex; +} + +static +void posix_mutex_free( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + pthread_mutex_destroy(mutex); + ecs_os_free(mutex); +} + +static +void posix_mutex_lock( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_lock(mutex)) { + abort(); + } +} + +static +void posix_mutex_unlock( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_unlock(mutex)) { + abort(); + } +} + +static +ecs_os_cond_t posix_cond_new(void) { + pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); + if (pthread_cond_init(cond, NULL)) { + abort(); + } + return (ecs_os_cond_t)(uintptr_t)cond; +} + +static +void posix_cond_free( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_destroy(cond)) { + abort(); + } + ecs_os_free(cond); +} + +static +void posix_cond_signal( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_signal(cond)) { + abort(); + } +} + +static +void posix_cond_broadcast( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_broadcast(cond)) { + abort(); + } +} + +static +void posix_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_cond_wait(cond, mutex)) { + abort(); + } +} + +static bool posix_time_initialized; + +#if defined(__APPLE__) && defined(__MACH__) +static mach_timebase_info_data_t posix_osx_timebase; +static uint64_t posix_time_start; +#else +static uint64_t posix_time_start; +#endif + +static +void posix_time_setup(void) { + if (posix_time_initialized) { + return; + } + + posix_time_initialized = true; + + #if defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&posix_osx_timebase); + posix_time_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +static +void posix_sleep( + int32_t sec, + int32_t nanosec) +{ + 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_err("nanosleep failed"); + } +} + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(ECS_TARGET_DARWIN) +static +int64_t posix_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 + +static +uint64_t posix_time_now(void) { + ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t now; + + #if defined(ECS_TARGET_DARWIN) + now = (uint64_t) posix_int64_muldiv( + (int64_t)mach_absolute_time(), + (int64_t)posix_osx_timebase.numer, + (int64_t)posix_osx_timebase.denom); + #elif defined(__EMSCRIPTEN__) + now = (long long)(emscripten_get_now() * 1000.0 * 1000); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); + #endif + + return now; +} + +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = posix_thread_new; + api.thread_join_ = posix_thread_join; + api.thread_self_ = posix_thread_self; + api.task_new_ = posix_thread_new; + api.task_join_ = posix_thread_join; + api.ainc_ = posix_ainc; + api.adec_ = posix_adec; + api.lainc_ = posix_lainc; + api.ladec_ = posix_ladec; + api.mutex_new_ = posix_mutex_new; + api.mutex_free_ = posix_mutex_free; + api.mutex_lock_ = posix_mutex_lock; + api.mutex_unlock_ = posix_mutex_unlock; + api.cond_new_ = posix_cond_new; + api.cond_free_ = posix_cond_free; + api.cond_signal_ = posix_cond_signal; + api.cond_broadcast_ = posix_cond_broadcast; + api.cond_wait_ = posix_cond_wait; + api.sleep_ = posix_sleep; + api.now_ = posix_time_now; + + posix_time_setup(); + + ecs_os_set_api(&api); +} + +#endif +#endif + +/** + * @file addons/ipeline/pipeline.c + * @brief Functions for building and running pipelines. + */ + + +#ifdef FLECS_PIPELINE + +static void flecs_pipeline_free( + ecs_pipeline_state_t *p) +{ + if (p) { + ecs_world_t *world = p->query->filter.world; + ecs_allocator_t *a = &world->allocator; + ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t); + ecs_vec_fini_t(a, &p->systems, ecs_entity_t); + ecs_os_free(p->iters); + ecs_query_fini(p->query); + ecs_os_free(p); + } +} + +static ECS_MOVE(EcsPipeline, dst, src, { + flecs_pipeline_free(dst->state); + dst->state = src->state; + src->state = NULL; +}) + +static ECS_DTOR(EcsPipeline, ptr, { + flecs_pipeline_free(ptr->state); +}) + +typedef enum ecs_write_kind_t { + WriteStateNone = 0, + WriteStateToStage, +} ecs_write_kind_t; + +typedef struct ecs_write_state_t { + bool write_barrier; + ecs_map_t ids; + ecs_map_t wildcard_ids; +} ecs_write_state_t; + +static +ecs_write_kind_t flecs_pipeline_get_write_state( + ecs_write_state_t *write_state, + ecs_id_t id) +{ + ecs_write_kind_t result = WriteStateNone; + + if (write_state->write_barrier) { + /* Any component could have been written */ + return WriteStateToStage; + } + + if (id == EcsWildcard) { + /* Using a wildcard for id indicates read barrier. Return true if any + * components could have been staged */ + if (ecs_map_count(&write_state->ids) || + ecs_map_count(&write_state->wildcard_ids)) + { + return WriteStateToStage; + } + } + + if (!ecs_id_is_wildcard(id)) { + if (ecs_map_get(&write_state->ids, id)) { + result = WriteStateToStage; + } + } else { + ecs_map_iter_t it = ecs_map_iter(&write_state->ids); + while (ecs_map_next(&it)) { + if (ecs_id_match(ecs_map_key(&it), id)) { + return WriteStateToStage; + } + } + } + + if (ecs_map_count(&write_state->wildcard_ids)) { + ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids); + while (ecs_map_next(&it)) { + if (ecs_id_match(id, ecs_map_key(&it))) { + return WriteStateToStage; + } + } + } + + return result; +} + +static +void flecs_pipeline_set_write_state( + ecs_write_state_t *write_state, + ecs_id_t id) +{ + if (id == EcsWildcard) { + /* If writing to wildcard, flag all components as written */ + write_state->write_barrier = true; + return; + } + + ecs_map_t *ids; + if (ecs_id_is_wildcard(id)) { + ids = &write_state->wildcard_ids; + } else { + ids = &write_state->ids; + } + + ecs_map_ensure(ids, id)[0] = true; +} + +static +void flecs_pipeline_reset_write_state( + ecs_write_state_t *write_state) +{ + ecs_map_clear(&write_state->ids); + ecs_map_clear(&write_state->wildcard_ids); + write_state->write_barrier = false; +} + +static +bool flecs_pipeline_check_term( + ecs_world_t *world, + ecs_term_t *term, + bool is_active, + ecs_write_state_t *write_state) +{ + (void)world; + + ecs_term_id_t *src = &term->src; + if (src->flags & EcsInOutNone) { + return false; + } + + ecs_id_t id = term->id; + ecs_oper_kind_t oper = term->oper; + ecs_inout_kind_t inout = term->inout; + bool from_any = ecs_term_match_0(term); + bool from_this = ecs_term_match_this(term); + bool is_shared = !from_any && (!from_this || !(src->flags & EcsSelf)); + + ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); + + if (from_this && ws >= WriteStateToStage) { + /* A staged write could have happened for an id that's matched on the + * main storage. Even if the id isn't read, still insert a merge so that + * a write to the main storage after the staged write doesn't get + * overwritten. */ + return true; + } + + if (inout == EcsInOutDefault) { + if (from_any) { + /* If no inout kind is specified for terms without a source, this is + * not interpreted as a read/write annotation but just a (component) + * id that's passed to a system. */ + return false; + } else if (is_shared) { + inout = EcsIn; + } else { + /* Default for owned terms is InOut */ + inout = EcsInOut; + } + } + + if (oper == EcsNot && inout == EcsOut) { + /* If a Not term is combined with Out, it signals that the system + * intends to add a component that the entity doesn't yet have */ + from_any = true; + } + + if (from_any) { + switch(inout) { + case EcsOut: + case EcsInOut: + if (is_active) { + /* Only flag component as written if system is active */ + flecs_pipeline_set_write_state(write_state, id); + } + break; + case EcsInOutDefault: + case EcsInOutNone: + case EcsIn: + break; + } + + switch(inout) { + case EcsIn: + case EcsInOut: + if (ws == WriteStateToStage) { + /* If a system does a get/get_mut, the component is fetched from + * the main store so it must be merged first */ + return true; + } + /* fall through */ + case EcsInOutDefault: + case EcsInOutNone: + case EcsOut: + break; + } + } + + return false; +} + +static +bool flecs_pipeline_check_terms( + ecs_world_t *world, + ecs_filter_t *filter, + bool is_active, + ecs_write_state_t *ws) +{ + bool needs_merge = false; + ecs_term_t *terms = filter->terms; + int32_t t, term_count = filter->term_count; + + /* Check This terms first. This way if a term indicating writing to a stage + * was added before the term, it won't cause merging. */ + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (ecs_term_match_this(term)) { + needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); + } + } + + /* Now check staged terms */ + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (!ecs_term_match_this(term)) { + needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); + } + } + + return needs_merge; +} + +static +EcsPoly* flecs_pipeline_term_system( + ecs_iter_t *it) +{ + int32_t index = ecs_table_get_column_index( + it->real_world, it->table, ecs_poly_id(EcsSystem)); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); + ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); + return poly; +} + +static +bool flecs_pipeline_build( + ecs_world_t *world, + ecs_pipeline_state_t *pq) +{ + ecs_iter_t it = ecs_query_iter(world, pq->query); + + if (pq->match_count == pq->query->match_count) { + /* No need to rebuild the pipeline */ + ecs_iter_fini(&it); + return false; + } + + world->info.pipeline_build_count_total ++; + pq->rebuild_count ++; + + ecs_allocator_t *a = &world->allocator; + ecs_pipeline_op_t *op = NULL; + ecs_write_state_t ws = {0}; + ecs_map_init(&ws.ids, a); + ecs_map_init(&ws.wildcard_ids, a); + + ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); + ecs_vec_reset_t(a, &pq->systems, ecs_entity_t); + + bool multi_threaded = false; + bool no_readonly = false; + bool first = true; + + /* Iterate systems in pipeline, add ops for running / merging */ + while (ecs_query_next(&it)) { + EcsPoly *poly = flecs_pipeline_term_system(&it); + bool is_active = ecs_table_get_type_index(world, it.table, EcsEmpty) == -1; + + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_poly_assert(poly[i].poly, ecs_system_t); + ecs_system_t *sys = (ecs_system_t*)poly[i].poly; + ecs_query_t *q = sys->query; + + bool needs_merge = false; + needs_merge = flecs_pipeline_check_terms( + world, &q->filter, is_active, &ws); + + if (is_active) { + if (first) { + multi_threaded = sys->multi_threaded; + no_readonly = sys->no_readonly; + first = false; + } + + if (sys->multi_threaded != multi_threaded) { + needs_merge = true; + multi_threaded = sys->multi_threaded; + } + if (sys->no_readonly != no_readonly) { + needs_merge = true; + no_readonly = sys->no_readonly; + } + } + + if (no_readonly) { + needs_merge = true; + } + + if (needs_merge) { + /* After merge all components will be merged, so reset state */ + flecs_pipeline_reset_write_state(&ws); + + /* An inactive system can insert a merge if one of its + * components got written, which could make the system + * active. If this is the only system in the pipeline operation, + * it results in an empty operation when we get here. If that's + * the case, reuse the empty operation for the next op. */ + if (op && op->count) { + 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) { + needs_merge = flecs_pipeline_check_terms( + world, &q->filter, 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_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); + op->offset = ecs_vec_count(&pq->systems); + op->count = 0; + op->multi_threaded = false; + op->no_readonly = false; + op->time_spent = 0; + op->commands_enqueued = 0; + } + + /* Don't increase count for inactive systems, as they are ignored by + * the query used to run the pipeline. */ + if (is_active) { + ecs_vec_append_t(a, &pq->systems, ecs_entity_t)[0] = + it.entities[i]; + if (!op->count) { + op->multi_threaded = multi_threaded; + op->no_readonly = no_readonly; + } + op->count ++; + } + } + } + + if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { + ecs_vec_remove_last(&pq->ops); + } + + ecs_map_fini(&ws.ids); + ecs_map_fini(&ws.wildcard_ids); + + op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); + + if (!op) { + ecs_dbg("#[green]pipeline#[reset] is empty"); + return true; + } else { + /* Add schedule to debug tracing */ + ecs_dbg("#[bold]pipeline rebuild"); + ecs_log_push_1(); + + ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", + op->multi_threaded, !op->no_readonly); + ecs_log_push_1(); + + int32_t i, count = ecs_vec_count(&pq->systems); + int32_t op_index = 0, ran_since_merge = 0; + ecs_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); + for (i = 0; i < count; i ++) { + ecs_entity_t system = systems[i]; + const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); + ecs_poly_assert(poly->poly, ecs_system_t); + ecs_system_t *sys = (ecs_system_t*)poly->poly; + +#ifdef FLECS_LOG_1 + char *path = ecs_get_fullpath(world, system); + const char *doc_name = NULL; +#ifdef FLECS_DOC + const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, + EcsDocDescription, EcsName); + if (doc_name_id) { + doc_name = doc_name_id->value; + } +#endif + if (doc_name) { + ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); + } else { + ecs_dbg("#[green]system#[reset] %s", path); + } + ecs_os_free(path); +#endif + + ecs_assert(op[op_index].offset + ran_since_merge == i, + ECS_INTERNAL_ERROR, NULL); + + ran_since_merge ++; + if (ran_since_merge == op[op_index].count) { + ecs_dbg("#[magenta]merge#[reset]"); + ecs_log_pop_1(); + ran_since_merge = 0; + op_index ++; + if (op_index < ecs_vec_count(&pq->ops)) { + ecs_dbg( + "#[green]schedule#[reset]: " + "threading: %d, staging: %d:", + op[op_index].multi_threaded, + !op[op_index].no_readonly); + } + ecs_log_push_1(); + } + + if (sys->last_frame == (world->info.frame_count_total + 1)) { + if (op_index < ecs_vec_count(&pq->ops)) { + pq->cur_op = &op[op_index]; + pq->cur_i = i; + } else { + pq->cur_op = NULL; + pq->cur_i = 0; + } + } + } + + ecs_log_pop_1(); + ecs_log_pop_1(); + } + + pq->match_count = pq->query->match_count; + + ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t), + ECS_INTERNAL_ERROR, NULL); + + return true; +} + +static +void flecs_pipeline_next_system( + ecs_pipeline_state_t *pq) +{ + if (!pq->cur_op) { + return; + } + + pq->cur_i ++; + if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) { + pq->cur_op ++; + if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) { + pq->cur_op = NULL; + } + } +} + +bool flecs_pipeline_update( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + bool start_of_frame) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); + + /* If any entity mutations happened that could have affected query matching + * notify appropriate queries so caches are up to date. This includes the + * pipeline query. */ + if (start_of_frame) { + ecs_run_aperiodic(world, 0); + } + + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + bool rebuilt = flecs_pipeline_build(world, pq); + if (start_of_frame) { + /* Initialize iterators */ + int32_t i, count = pq->iter_count; + for (i = 0; i < count; i ++) { + ecs_world_t *stage = ecs_get_stage(world, i); + pq->iters[i] = ecs_query_iter(stage, pq->query); + } + pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); + pq->cur_i = 0; + } else { + flecs_pipeline_next_system(pq); + } + + return rebuilt; +} + +void ecs_run_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_ftime_t delta_time) +{ + if (!pipeline) { + pipeline = world->pipeline; + } + + /* create any worker task threads request */ + if (ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); + } + + EcsPipeline *p = + ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); + flecs_workers_progress(world, p->state, delta_time); + + if (ecs_using_task_threads(world)) { + /* task threads were temporary and may now be joined */ + flecs_join_worker_threads(world); + } +} + +int32_t flecs_run_pipeline_ops( + ecs_world_t* world, + ecs_stage_t* stage, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time) +{ + ecs_pipeline_state_t* pq = world->pq; + ecs_pipeline_op_t* op = pq->cur_op; + int32_t i = pq->cur_i; + + ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL); + + int32_t count = ecs_vec_count(&pq->systems); + ecs_entity_t* systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); + int32_t ran_since_merge = i - op->offset; + + for (; i < count; i++) { + ecs_entity_t system = systems[i]; + const EcsPoly* poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); + ecs_poly_assert(poly->poly, ecs_system_t); + ecs_system_t* sys = (ecs_system_t*)poly->poly; + + /* Keep track of the last frame for which the system has ran, so we + * know from where to resume the schedule in case the schedule + * changes during a merge. */ + sys->last_frame = world->info.frame_count_total + 1; + + ecs_stage_t* s = NULL; + if (!op->no_readonly) { + /* If system is no_readonly it operates on the actual world, not + * the stage. Only pass stage to system if it's readonly. */ + s = stage; + } + + ecs_run_intern(world, s, system, sys, stage_index, + stage_count, delta_time, 0, 0, NULL); + + world->info.systems_ran_frame++; + ran_since_merge++; + + if (ran_since_merge == op->count) { + /* Merge */ + break; + } + } + + return i; +} + +void flecs_run_pipeline( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(world, ecs_stage_t); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); + int32_t stage_count = ecs_get_stage_count(world); + + ecs_assert(!stage_index, ECS_INVALID_OPERATION, NULL); + + bool multi_threaded = ecs_get_stage_count(world) > 1; + + // Update the pipeline the workers will execute + world->pq = pq; + + // Update the pipeline before waking the workers. + flecs_pipeline_update(world, pq, true); + + // If there are no operations to execute in the pipeline bail early, + // no need to wake the workers since they have nothing to do. + while (pq->cur_op != NULL) { + if (pq->cur_i == ecs_vec_count(&pq->systems)) { + flecs_pipeline_update(world, pq, false); + continue; + } + + bool no_readonly = pq->cur_op->no_readonly; + bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; + + pq->no_readonly = no_readonly; + + if (!no_readonly) { + ecs_readonly_begin(world); + } + + ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); + ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); + + if (op_multi_threaded) { + flecs_signal_workers(world); + } + + ecs_time_t st = { 0 }; + bool measure_time = world->flags & EcsWorldMeasureSystemTime; + if (measure_time) { + ecs_time_measure(&st); + } + + const int32_t i = flecs_run_pipeline_ops( + world, stage, stage_index, stage_count, delta_time); + + if (measure_time) { + /* Don't include merge time in system time */ + world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); + } + + if (op_multi_threaded) { + flecs_wait_for_sync(world); + } + + if (!no_readonly) { + ecs_time_t mt = { 0 }; + if (measure_time) { + ecs_time_measure(&mt); + } + + int32_t si; + for (si = 0; si < stage_count; si ++) { + ecs_stage_t *s = &world->stages[si]; + pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue); + } + + ecs_readonly_end(world); + if (measure_time) { + pq->cur_op->time_spent += ecs_time_measure(&mt); + } + } + + /* Store the current state of the schedule after we synchronized the + * threads, to avoid race conditions. */ + pq->cur_i = i; + + flecs_pipeline_update(world, pq, false); + } +} + +static +void flecs_run_startup_systems( + ecs_world_t *world) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_dependson(EcsOnStart)); + if (!idr || !flecs_table_cache_count(&idr->cache)) { + /* Don't bother creating startup pipeline if no systems exist */ + return; + } + + ecs_dbg_2("#[bold]startup#[reset]"); + ecs_log_push_2(); + int32_t stage_count = world->stage_count; + world->stage_count = 1; /* Prevents running startup systems on workers */ + + /* Creating a pipeline is relatively expensive, but this only happens + * for the first frame. The startup pipeline is deleted afterwards, which + * eliminates the overhead of keeping its query cache in sync. */ + ecs_dbg_2("#[bold]create startup pipeline#[reset]"); + ecs_log_push_2(); + ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ + .query = { + .filter.terms = { + { .id = EcsSystem }, + { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, + { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn }, + { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, + { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } + }, + .order_by = flecs_entity_compare + } + }); + ecs_log_pop_2(); + + /* Run & delete pipeline */ + ecs_dbg_2("#[bold]run startup systems#[reset]"); + ecs_log_push_2(); + ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); + const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); + ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); + flecs_workers_progress(world, p->state, 0); + ecs_log_pop_2(); + + ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); + ecs_log_push_2(); + ecs_delete(world, start_pip); + ecs_log_pop_2(); + + world->stage_count = stage_count; + ecs_log_pop_2(); + +error: + return; +} + +bool ecs_progress( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); + + /* If this is the first frame, run startup systems */ + if (world->info.frame_count_total == 0) { + flecs_run_startup_systems(world); + } + + /* create any worker task threads request */ + if (ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); + } + + ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); + ecs_log_push_3(); + const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); + ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); + flecs_workers_progress(world, p->state, delta_time); + ecs_log_pop_3(); + + ecs_frame_end(world); + + if (ecs_using_task_threads(world)) { + /* task threads were temporary and may now be joined */ + flecs_join_worker_threads(world); + } + + return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +error: + return false; +} + +void ecs_set_time_scale( + ecs_world_t *world, + ecs_ftime_t scale) +{ + world->info.time_scale = scale; +} + +void ecs_reset_clock( + ecs_world_t *world) +{ + world->info.world_time_total = 0; + world->info.world_time_total_raw = 0; +} + +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, + ECS_INVALID_PARAMETER, "not a pipeline"); + + world->pipeline = pipeline; +error: + return; +} + +ecs_entity_t ecs_get_pipeline( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->pipeline; +error: + return 0; +} + +ecs_entity_t ecs_pipeline_init( + ecs_world_t *world, + const ecs_pipeline_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world, 0); + } + + ecs_query_desc_t qd = desc->query; + if (!qd.order_by) { + qd.order_by = flecs_entity_compare; + } + qd.filter.entity = result; + + ecs_query_t *query = ecs_query_init(world, &qd); + if (!query) { + ecs_delete(world, result); + return 0; + } + + ecs_check(query->filter.terms != NULL, ECS_INVALID_PARAMETER, + "pipeline query cannot be empty"); + ecs_check(query->filter.terms[0].id == EcsSystem, + ECS_INVALID_PARAMETER, "pipeline must start with System term"); + + ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); + pq->query = query; + pq->match_count = -1; + pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty); + ecs_set(world, result, EcsPipeline, { pq }); + + return result; +error: + return 0; +} + +/* -- Module implementation -- */ + +static +void FlecsPipelineFini( + ecs_world_t *world, + void *ctx) +{ + (void)ctx; + if (ecs_get_stage_count(world)) { + ecs_set_threads(world, 0); + } + + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); +} + +#define flecs_bootstrap_phase(world, phase, depends_on)\ + flecs_bootstrap_tag(world, phase);\ + flecs_bootstrap_phase_(world, phase, depends_on) +static +void flecs_bootstrap_phase_( + ecs_world_t *world, + ecs_entity_t phase, + ecs_entity_t depends_on) +{ + ecs_add_id(world, phase, EcsPhase); + if (depends_on) { + ecs_add_pair(world, phase, EcsDependsOn, depends_on); + } +} + +void FlecsPipelineImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsPipeline); + ECS_IMPORT(world, FlecsSystem); +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsPipeline), + "Module that schedules and runs systems"); +#endif + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsPipeline); + flecs_bootstrap_tag(world, EcsPhase); + + /* Create anonymous phases to which the builtin phases will have DependsOn + * relationships. This ensures that, for example, EcsOnUpdate doesn't have a + * direct DependsOn relationship on EcsPreUpdate, which ensures that when + * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ + ecs_entity_t phase_0 = ecs_new(world, 0); + ecs_entity_t phase_1 = ecs_new_w_pair(world, EcsDependsOn, phase_0); + ecs_entity_t phase_2 = ecs_new_w_pair(world, EcsDependsOn, phase_1); + ecs_entity_t phase_3 = ecs_new_w_pair(world, EcsDependsOn, phase_2); + ecs_entity_t phase_4 = ecs_new_w_pair(world, EcsDependsOn, phase_3); + ecs_entity_t phase_5 = ecs_new_w_pair(world, EcsDependsOn, phase_4); + ecs_entity_t phase_6 = ecs_new_w_pair(world, EcsDependsOn, phase_5); + ecs_entity_t phase_7 = ecs_new_w_pair(world, EcsDependsOn, phase_6); + ecs_entity_t phase_8 = ecs_new_w_pair(world, EcsDependsOn, phase_7); + + flecs_bootstrap_phase(world, EcsOnStart, 0); + flecs_bootstrap_phase(world, EcsPreFrame, 0); + flecs_bootstrap_phase(world, EcsOnLoad, phase_0); + flecs_bootstrap_phase(world, EcsPostLoad, phase_1); + flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); + flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); + flecs_bootstrap_phase(world, EcsOnValidate, phase_4); + flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); + flecs_bootstrap_phase(world, EcsPreStore, phase_6); + flecs_bootstrap_phase(world, EcsOnStore, phase_7); + flecs_bootstrap_phase(world, EcsPostFrame, phase_8); + + ecs_set_hooks(world, EcsPipeline, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsPipeline), + .move = ecs_move(EcsPipeline) + }); + + world->pipeline = ecs_pipeline(world, { + .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), + .query = { + .filter.terms = { + { .id = EcsSystem }, + { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, + { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn, .oper = EcsNot }, + { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, + { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } + }, + .order_by = flecs_entity_compare + } + }); + + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); +} + +#endif + +/** + * @file addons/pipeline/worker.c + * @brief Functions for running pipelines on one or more threads. + */ + + +#ifdef FLECS_PIPELINE + +/* Synchronize workers */ +static +void flecs_sync_worker( + ecs_world_t* world) +{ + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + /* Signal that thread is waiting */ + ecs_os_mutex_lock(world->sync_mutex); + if (++world->workers_waiting == (stage_count - 1)) { + /* 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); +} + +/* Worker thread */ +static +void* flecs_worker(void *arg) { + ecs_stage_t *stage = arg; + ecs_world_t *world = stage->world; + + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); + + ecs_dbg_2("worker %d: start", stage->id); + + /* Start worker, increase counter so main thread knows how many + * workers are ready */ + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running ++; + + if (!(world->flags & EcsWorldQuitWorkers)) { + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + } + + ecs_os_mutex_unlock(world->sync_mutex); + + while (!(world->flags & EcsWorldQuitWorkers)) { + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + + ecs_dbg_3("worker %d: run", stage->id); + flecs_run_pipeline_ops(world, stage, stage->id, world->stage_count, + world->info.delta_time); + + ecs_set_scope((ecs_world_t*)stage, old_scope); + + flecs_sync_worker(world); + } + + ecs_dbg_2("worker %d: finalizing", stage->id); + + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running --; + ecs_os_mutex_unlock(world->sync_mutex); + + ecs_dbg_2("worker %d: stop", stage->id); + + return NULL; +} + +/* Start threads */ +void flecs_create_worker_threads( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + int32_t stages = ecs_get_stage_count(world); + + for (int32_t i = 1; i < stages; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_poly_assert(stage, ecs_stage_t); + + ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL); + if (ecs_using_task_threads(world)) { + /* workers are using tasks in an external task manager provided to + * the OS API */ + stage->thread = ecs_os_task_new(flecs_worker, stage); + } else { + /* workers are using long-running os threads */ + stage->thread = ecs_os_thread_new(flecs_worker, stage); + } + ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); + } +} + +static +void flecs_start_workers( + ecs_world_t *world, + int32_t threads) +{ + ecs_set_stage_count(world, threads); + + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); + } +} + +/* Wait until all workers are running */ +static +void flecs_wait_for_workers( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + bool wait = true; + do { + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_running == (stage_count - 1)) { + wait = false; + } + ecs_os_mutex_unlock(world->sync_mutex); + } while (wait); +} + +/* Wait until all threads are waiting on sync point */ +void flecs_wait_for_sync( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); + + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_waiting != (stage_count - 1)) { + ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + } + + /* We shouldn't have been signalled unless all workers are waiting on sync */ + ecs_assert(world->workers_waiting == (stage_count - 1), + ECS_INTERNAL_ERROR, NULL); + + world->workers_waiting = 0; + ecs_os_mutex_unlock(world->sync_mutex); + + ecs_dbg_3("#[bold]pipeline: workers synced"); +} + +/* Signal workers that they can start/resume work */ +void flecs_signal_workers( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + ecs_dbg_3("#[bold]pipeline: signal workers"); + ecs_os_mutex_lock(world->sync_mutex); + ecs_os_cond_broadcast(world->worker_cond); + ecs_os_mutex_unlock(world->sync_mutex); +} + +void flecs_join_worker_threads( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); + bool threads_active = false; + + /* Test if threads are created. Cannot use workers_running, since this is + * a potential race if threads haven't spun up yet. */ + ecs_stage_t *stages = world->stages; + int i, count = world->stage_count; + for (i = 1; i < count; i ++) { + ecs_stage_t *stage = &stages[i]; + if (stage->thread) { + threads_active = true; + break; + } + }; + + /* If no threads are active, just return */ + if (!threads_active) { + return; + } + + /* Make sure all threads are running, to ensure they catch the signal */ + flecs_wait_for_workers(world); + + /* Signal threads should quit */ + world->flags |= EcsWorldQuitWorkers; + flecs_signal_workers(world); + + /* Join all threads with main */ + for (i = 1; i < count; i ++) { + if (ecs_using_task_threads(world)) { + ecs_os_task_join(stages[i].thread); + } else { + ecs_os_thread_join(stages[i].thread); + } + stages[i].thread = 0; + } + + world->flags &= ~EcsWorldQuitWorkers; + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); +} + +/* -- Private functions -- */ +void flecs_workers_progress( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); + + /* Make sure workers are running and ready */ + flecs_wait_for_workers(world); + + /* Run pipeline on main thread */ + ecs_world_t *stage = ecs_get_stage(world, 0); + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + flecs_run_pipeline(stage, pq, delta_time); + ecs_set_scope((ecs_world_t*)stage, old_scope); +} + +static +void flecs_set_threads_internal( + ecs_world_t *world, + int32_t threads, + bool use_task_api) +{ + ecs_assert(threads <= 1 || (use_task_api + ? ecs_os_has_task_support() + : ecs_os_has_threading()), + ECS_MISSING_OS_API, NULL); + + int32_t stage_count = ecs_get_stage_count(world); + bool worker_method_changed = (use_task_api != world->workers_use_task_api); + + if ((stage_count != threads) || worker_method_changed) { + /* Stop existing threads */ + if (stage_count > 1) { + flecs_join_worker_threads(world); + ecs_set_stage_count(world, 1); + + if (world->worker_cond) { + ecs_os_cond_free(world->worker_cond); + } + if (world->sync_cond) { + ecs_os_cond_free(world->sync_cond); + } + if (world->sync_mutex) { + ecs_os_mutex_free(world->sync_mutex); + } + } + + world->workers_use_task_api = use_task_api; + + /* 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(); + flecs_start_workers(world, threads); + } + } +} + +/* -- Public functions -- */ + +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + flecs_set_threads_internal(world, threads, false /* use thread API */); +} + +void ecs_set_task_threads( + ecs_world_t *world, + int32_t task_threads) +{ + flecs_set_threads_internal(world, task_threads, true /* use task API */); +} + +bool ecs_using_task_threads( + ecs_world_t *world) +{ + return world->workers_use_task_api; +} + +#endif + + /** + * @file addons/rules/api.c + * @brief User facing API for rules. + */ + + /** + * @file addons/rules/rules.h + * @brief Internal types and functions for rules addon. + */ + + +#ifdef FLECS_RULES + +typedef uint8_t ecs_var_id_t; +typedef int16_t ecs_rule_lbl_t; +typedef ecs_flags64_t ecs_write_flags_t; + +#define EcsRuleMaxVarCount (64) +#define EcsVarNone ((ecs_var_id_t)-1) +#define EcsThisName "this" + +/* -- Variable types -- */ +typedef enum { + EcsVarEntity, /* Variable that stores an entity id */ + EcsVarTable, /* Variable that stores a table */ + EcsVarAny /* Used when requesting either entity or table var */ +} ecs_var_kind_t; + +typedef struct ecs_rule_var_t { + int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable) */ + bool anonymous; /* variable is anonymous */ + ecs_var_id_t id; /* variable id */ + ecs_var_id_t table_id; /* id to table variable, if any */ + ecs_var_id_t base_id; /* id to base entity variable, for lookups */ + const char *name; /* variable name */ + const char *lookup; /* Lookup string for variable */ +#ifdef FLECS_DEBUG + const char *label; /* for debugging */ +#endif +} ecs_rule_var_t; + +/* -- Instruction kinds -- */ +typedef enum { + EcsRuleAnd, /* And operator: find or match id against variable source */ + EcsRuleAndId, /* And operator for fixed id (no wildcards/variables) */ + EcsRuleAndAny, /* And operator with support for matching Any src/id */ + EcsRuleTriv, /* Trivial search */ + EcsRuleTrivData, /* Trivial search with setting data fields */ + EcsRuleTrivWildcard, /* Trivial search with (exclusive) wildcard ids */ + EcsRuleSelectAny, /* Dedicated instruction for _ queries where the src is unknown */ + EcsRuleUp, /* Up traversal */ + EcsRuleUpId, /* Up traversal for fixed id (like AndId) */ + EcsRuleSelfUp, /* Self|up traversal */ + EcsRuleSelfUpId, /* Self|up traversal for fixed id (like AndId) */ + EcsRuleWith, /* Match id against fixed or variable source */ + EcsRuleTrav, /* Support for transitive/reflexive queries */ + EcsRuleIds, /* Test for existence of ids matching wildcard */ + EcsRuleIdsRight, /* Find ids in use that match (R, *) wildcard */ + EcsRuleIdsLeft, /* Find ids in use that match (*, T) wildcard */ + EcsRuleEach, /* Iterate entities in table, populate entity variable */ + EcsRuleStore, /* Store table or entity in variable */ + EcsRuleReset, /* Reset value of variable to wildcard (*) */ + EcsRuleOr, /* Or operator */ + EcsRuleOptional, /* Optional operator */ + EcsRuleIf, /* Conditional execution */ + EcsRuleNot, /* Sets iterator state after term was not matched */ + EcsRuleEnd, /* End of control flow block */ + EcsRulePredEq, /* Test if variable is equal to, or assign to if not set */ + EcsRulePredNeq, /* Test if variable is not equal to */ + EcsRulePredEqName, /* Same as EcsRulePredEq but with matching by name */ + EcsRulePredNeqName, /* Same as EcsRulePredNeq but with matching by name */ + EcsRulePredEqMatch, /* Same as EcsRulePredEq but with fuzzy matching by name */ + EcsRulePredNeqMatch, /* Same as EcsRulePredNeq but with fuzzy matching by name */ + EcsRuleLookup, /* Lookup relative to variable */ + EcsRuleSetVars, /* Populate it.sources from variables */ + EcsRuleSetThis, /* Populate This entity variable */ + EcsRuleSetFixed, /* Set fixed source entity ids */ + EcsRuleSetIds, /* Set fixed (component) ids */ + EcsRuleSetId, /* Set id if not set */ + EcsRuleContain, /* Test if table contains entity */ + EcsRulePairEq, /* Test if both elements of pair are the same */ + EcsRulePopulate, /* Populate any data fields */ + EcsRulePopulateSelf, /* Populate only self (owned) data fields */ + EcsRuleYield, /* Yield result back to application */ + EcsRuleNothing /* Must be last */ +} ecs_rule_op_kind_t; + +/* Op flags to indicate if ecs_rule_ref_t is entity or variable */ +#define EcsRuleIsEntity (1 << 0) +#define EcsRuleIsVar (1 << 1) +#define EcsRuleIsSelf (1 << 6) + +/* Op flags used to shift EcsRuleIsEntity and EcsRuleIsVar */ +#define EcsRuleSrc 0 +#define EcsRuleFirst 2 +#define EcsRuleSecond 4 + +/* References to variable or entity */ +typedef union { + ecs_var_id_t var; + ecs_entity_t entity; +} ecs_rule_ref_t; + +/* Query instruction */ +typedef struct ecs_rule_op_t { + uint8_t kind; /* Instruction kind */ + ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ + int8_t field_index; /* Query field corresponding with operation */ + int8_t term_index; /* Query term corresponding with operation */ + ecs_rule_lbl_t prev; /* Backtracking label (no data) */ + ecs_rule_lbl_t next; /* Forwarding label. Must come after prev */ + ecs_rule_lbl_t other; /* Misc register used for control flow */ + ecs_flags16_t match_flags; /* Flags that modify matching behavior */ + ecs_rule_ref_t src; + ecs_rule_ref_t first; + ecs_rule_ref_t second; + ecs_flags64_t written; /* Bitset with variables written by op */ +} ecs_rule_op_t; + + /* And context */ +typedef struct { + ecs_id_record_t *idr; + ecs_table_cache_iter_t it; + int16_t column; + int16_t remaining; +} ecs_rule_and_ctx_t; + +/* Down traversal cache (for resolving up queries w/unknown source) */ +typedef struct { + ecs_table_t *table; + bool leaf; /* Table owns and inherits id (for Up queries without Self) */ +} ecs_trav_down_elem_t; + +typedef struct { + ecs_vec_t elems; /* vector */ + bool ready; +} ecs_trav_down_t; + +typedef struct { + ecs_entity_t src; + ecs_id_t id; + int32_t column; + bool ready; +} ecs_trav_up_t; + +typedef struct { + ecs_map_t src; /* map or map */ + ecs_id_t with; + ecs_flags32_t dir; +} ecs_trav_up_cache_t; + +/* And up context */ +typedef struct { + ecs_rule_and_ctx_t and; + ecs_table_t *table; + int32_t row; + int32_t end; + ecs_entity_t trav; + ecs_id_t with; + ecs_id_t matched; + ecs_id_record_t *idr_with; + ecs_id_record_t *idr_trav; + ecs_trav_down_t *down; + int32_t cache_elem; + ecs_trav_up_cache_t cache; +} ecs_rule_up_ctx_t; + +/* Cache for storing results of upward/downward "all" traversal. This type of + * traversal iterates and caches the entire tree. */ +typedef struct { + ecs_entity_t entity; + ecs_id_record_t *idr; + int32_t column; +} ecs_trav_elem_t; + +typedef struct { + ecs_id_t id; + ecs_id_record_t *idr; + ecs_vec_t entities; + bool up; +} ecs_trav_cache_t; + +/* Trav context */ +typedef struct { + ecs_rule_and_ctx_t and; + int32_t index; + int32_t offset; + int32_t count; + ecs_trav_cache_t cache; + bool yield_reflexive; +} ecs_rule_trav_ctx_t; + + /* Eq context */ +typedef struct { + ecs_table_range_t range; + int32_t index; + int16_t name_col; + bool redo; +} ecs_rule_eq_ctx_t; + + /* Each context */ +typedef struct { + int32_t row; +} ecs_rule_each_ctx_t; + + /* Setthis context */ +typedef struct { + ecs_table_range_t range; +} ecs_rule_setthis_ctx_t; + +/* Ids context */ +typedef struct { + ecs_id_record_t *cur; +} ecs_rule_ids_ctx_t; + +/* Control flow context */ +typedef struct { + ecs_rule_lbl_t op_index; + ecs_id_t field_id; +} ecs_rule_ctrl_ctx_t; + +/* Trivial context */ +typedef struct { + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; +} ecs_rule_trivial_ctx_t; + +typedef struct ecs_rule_op_ctx_t { + union { + ecs_rule_and_ctx_t and; + ecs_rule_up_ctx_t up; + ecs_rule_trav_ctx_t trav; + ecs_rule_ids_ctx_t ids; + ecs_rule_eq_ctx_t eq; + ecs_rule_each_ctx_t each; + ecs_rule_setthis_ctx_t setthis; + ecs_rule_ctrl_ctx_t ctrl; + ecs_rule_trivial_ctx_t trivial; + } is; +} ecs_rule_op_ctx_t; + +typedef struct { + /* Labels used for control flow */ + ecs_rule_lbl_t lbl_query; /* Used to find the op that does the actual searching */ + ecs_rule_lbl_t lbl_begin; + ecs_rule_lbl_t lbl_cond_eval; + ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ + bool in_or; /* Whether we're in an or chain */ +} ecs_rule_compile_ctrlflow_t; + +/* Rule compiler state */ +typedef struct { + ecs_vec_t *ops; + ecs_write_flags_t written; /* Bitmask to check which variables have been written */ + ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ + + /* Maintain control flow per scope */ + ecs_rule_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; + ecs_rule_compile_ctrlflow_t *cur; /* Current scope */ + + int32_t scope; /* Nesting level of query scopes */ + ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */ +} ecs_rule_compile_ctx_t; + +/* Rule run state */ +typedef struct { + uint64_t *written; /* Bitset to check which variables have been written */ + ecs_rule_lbl_t op_index; /* Currently evaluated operation */ + ecs_var_t *vars; /* Variable storage */ + ecs_iter_t *it; /* Iterator */ + ecs_rule_op_ctx_t *op_ctx; /* Operation context (stack) */ + ecs_world_t *world; /* Reference to world */ + const ecs_rule_t *rule; /* Reference to rule */ + const ecs_rule_var_t *rule_vars; /* Reference to rule variable array */ + ecs_flags32_t *source_set; /* Whether ecs_iter_t::sources is written by instruction */ + ecs_rule_iter_t *rit; +} ecs_rule_run_ctx_t; + +typedef struct { + ecs_rule_var_t var; + const char *name; +} ecs_rule_var_cache_t; + +struct ecs_rule_t { + ecs_header_t hdr; /* Poly header */ + ecs_filter_t filter; /* Filter */ + + /* Variables */ + ecs_rule_var_t *vars; /* Variables */ + int32_t var_count; /* Number of variables */ + int32_t var_pub_count; /* Number of public variables */ + bool has_table_this; /* Does rule have [$this] */ + ecs_hashmap_t tvar_index; /* Name index for table variables */ + ecs_hashmap_t evar_index; /* Name index for entity variables */ + ecs_rule_var_cache_t vars_cache; /* For trivial rules with only This variables */ + char **var_names; /* Array with variable names for iterator */ + + ecs_var_id_t *src_vars; /* Array with ids to source variables for fields */ + ecs_rule_op_t *ops; /* Operations */ + int32_t op_count; /* Number of operations */ + + /* Mixins */ + ecs_iterable_t iterable; + ecs_poly_dtor_t dtor; + +#ifdef FLECS_DEBUG + int32_t var_size; /* Used for out of bounds check during compilation */ +#endif +}; + +/* Convert integer to label */ +ecs_rule_lbl_t flecs_itolbl( + int64_t val); + +/* Get ref flags (IsEntity) or IsVar) for ref (Src, First, Second) */ +ecs_flags16_t flecs_rule_ref_flags( + ecs_flags16_t flags, + ecs_flags16_t kind); + +/* Check if variable is written */ +bool flecs_rule_is_written( + ecs_var_id_t var_id, + uint64_t written); + +/* Check if ref is written (calls flecs_rule_is_written)*/ +bool flecs_ref_is_written( + const ecs_rule_op_t *op, + const ecs_rule_ref_t *ref, + ecs_flags16_t kind, + uint64_t written); + +/* Compile filter to list of operations */ +int flecs_rule_compile( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_rule_t *rule); + +/* Get allocator from iterator */ +ecs_allocator_t* flecs_rule_get_allocator( + const ecs_iter_t *it); + + +/* Traversal cache for transitive queries. Finds all reachable entities by + * following a relationship */ + +/* Find all entities when traversing downwards */ +void flecs_rule_get_trav_down_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity); + +/* Find all entities when traversing upwards */ +void flecs_rule_get_trav_up_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table); + +/* Free traversal cache */ +void flecs_rule_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache); + +/* Traversal caches for up traversal. Enables searching upwards until an entity + * with the queried for id has been found. */ + +/* Traverse downwards from starting entity to find all tables for which the + * specified entity is the source of the queried for id ('with'). */ +ecs_trav_down_t* flecs_rule_get_down_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_with, + bool self); + +/* Free down traversal cache */ +void flecs_rule_down_cache_fini( + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache); + +ecs_trav_up_t* flecs_rule_get_up_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_table_t *table, + ecs_id_t with, + ecs_entity_t trav, + ecs_id_record_t *idr_with, + ecs_id_record_t *idr_trav); + +/* Free up traversal cache */ +void flecs_rule_up_cache_fini( + ecs_trav_up_cache_t *cache); + +/* Convert instruction kind to string */ +const char* flecs_rule_op_str( + uint16_t kind); + +/* Iterator for trivial queries. */ +bool flecs_rule_trivial_search( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + ecs_rule_trivial_ctx_t *op_ctx, + bool first, + int32_t until); + +/* Iterator for trivial queries. */ +bool flecs_rule_trivial_search_nodata( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + ecs_rule_trivial_ctx_t *op_ctx, + bool first, + int32_t until); + +/* Iterator for trivial queries with wildcard matching. */ +bool flecs_rule_trivial_search_w_wildcards( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + ecs_rule_trivial_ctx_t *op_ctx, + bool first, + int32_t until); + +/* Trivial test for constrained $this. */ +bool flecs_rule_trivial_test( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + bool first, + int32_t term_count); + +/* Trivial test for constrained $this with wildcard matching. */ +bool flecs_rule_trivial_test_w_wildcards( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + bool first, + int32_t term_count); + +#endif + +#include + +#ifdef FLECS_RULES + +static ecs_mixins_t ecs_rule_t_mixins = { + .type_name = "ecs_rule_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_rule_t, filter.world), + [EcsMixinEntity] = offsetof(ecs_rule_t, filter.entity), + [EcsMixinIterable] = offsetof(ecs_rule_t, iterable), + [EcsMixinDtor] = offsetof(ecs_rule_t, dtor) + } +}; + +const char* flecs_rule_op_str( + uint16_t kind) +{ + switch(kind) { + case EcsRuleAnd: return "and "; + case EcsRuleAndId: return "andid "; + case EcsRuleAndAny: return "andany "; + case EcsRuleTriv: return "triv "; + case EcsRuleTrivData: return "trivpop "; + case EcsRuleTrivWildcard: return "trivwc "; + case EcsRuleSelectAny: return "any "; + case EcsRuleUp: return "up "; + case EcsRuleUpId: return "upid "; + case EcsRuleSelfUp: return "selfup "; + case EcsRuleSelfUpId: return "selfupid"; + case EcsRuleWith: return "with "; + case EcsRuleTrav: return "trav "; + case EcsRuleIds: return "ids "; + case EcsRuleIdsRight: return "idsr "; + case EcsRuleIdsLeft: return "idsl "; + case EcsRuleEach: return "each "; + case EcsRuleStore: return "store "; + case EcsRuleReset: return "reset "; + case EcsRuleOr: return "or "; + case EcsRuleOptional: return "option "; + case EcsRuleIf: return "if "; + case EcsRuleEnd: return "end "; + case EcsRuleNot: return "not "; + case EcsRulePredEq: return "eq "; + case EcsRulePredNeq: return "neq "; + case EcsRulePredEqName: return "eq_nm "; + case EcsRulePredNeqName: return "neq_nm "; + case EcsRulePredEqMatch: return "eq_m "; + case EcsRulePredNeqMatch: return "neq_m "; + case EcsRuleLookup: return "lookup "; + case EcsRuleSetVars: return "setvars "; + case EcsRuleSetThis: return "setthis "; + case EcsRuleSetFixed: return "setfix "; + case EcsRuleSetIds: return "setids "; + case EcsRuleSetId: return "setid "; + case EcsRuleContain: return "contain "; + case EcsRulePairEq: return "pair_eq "; + case EcsRulePopulate: return "pop "; + case EcsRulePopulateSelf: return "popself "; + case EcsRuleYield: return "yield "; + case EcsRuleNothing: return "nothing "; + default: return "!invalid"; + } +} + +/* Implementation for iterable mixin */ +static +void flecs_rule_iter_mixin_init( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter) +{ + ecs_poly_assert(poly, ecs_rule_t); + + if (filter) { + iter[1] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); + iter[0] = ecs_term_chain_iter(&iter[1], filter); + } else { + iter[0] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); + } +} + +static +void flecs_rule_fini( + ecs_rule_t *rule) +{ + if (rule->vars != &rule->vars_cache.var) { + ecs_os_free(rule->vars); + } + + ecs_os_free(rule->ops); + ecs_os_free(rule->src_vars); + flecs_name_index_fini(&rule->tvar_index); + flecs_name_index_fini(&rule->evar_index); + ecs_filter_fini(&rule->filter); + + ecs_poly_free(rule, ecs_rule_t); +} + +void ecs_rule_fini( + ecs_rule_t *rule) +{ + if (rule->filter.entity) { + /* If filter is associated with entity, use poly dtor path */ + ecs_delete(rule->filter.world, rule->filter.entity); + } else { + flecs_rule_fini(rule); + } +} + +ecs_rule_t* ecs_rule_init( + ecs_world_t *world, + const ecs_filter_desc_t *const_desc) +{ + ecs_rule_t *result = ecs_poly_new(ecs_rule_t); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + /* Initialize the query */ + ecs_filter_desc_t desc = *const_desc; + desc.storage = &result->filter; /* Use storage of rule */ + result->filter = ECS_FILTER_INIT; + if (ecs_filter_init(world, &desc) == NULL) { + goto error; + } + + result->iterable.init = flecs_rule_iter_mixin_init; + + /* Compile filter to operations */ + if (flecs_rule_compile(world, stage, result)) { + goto error; + } + + ecs_entity_t entity = const_desc->entity; + result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; + + if (entity) { + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t); + poly->poly = result; + ecs_poly_modified(world, entity, ecs_rule_t); + } + + return result; +error: + ecs_rule_fini(result); + return NULL; +} + +static +int32_t flecs_rule_op_ref_str( + const ecs_rule_t *rule, + ecs_rule_ref_t *ref, + ecs_flags16_t flags, + ecs_strbuf_t *buf) +{ + int32_t color_chars = 0; + if (flags & EcsRuleIsVar) { + ecs_assert(ref->var < rule->var_count, ECS_INTERNAL_ERROR, NULL); + ecs_rule_var_t *var = &rule->vars[ref->var]; + ecs_strbuf_appendlit(buf, "#[green]$#[reset]"); + if (var->kind == EcsVarTable) { + ecs_strbuf_appendch(buf, '['); + } + ecs_strbuf_appendlit(buf, "#[green]"); + if (var->name) { + ecs_strbuf_appendstr(buf, var->name); + } else { + if (var->id) { +#ifdef FLECS_DEBUG + if (var->label) { + ecs_strbuf_appendstr(buf, var->label); + ecs_strbuf_appendch(buf, '\''); + } +#endif + ecs_strbuf_append(buf, "%d", var->id); + } else { + ecs_strbuf_appendlit(buf, "this"); + } + } + ecs_strbuf_appendlit(buf, "#[reset]"); + if (var->kind == EcsVarTable) { + ecs_strbuf_appendch(buf, ']'); + } + color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]"); + } else if (flags & EcsRuleIsEntity) { + char *path = ecs_get_fullpath(rule->filter.world, ref->entity); + ecs_strbuf_appendlit(buf, "#[blue]"); + ecs_strbuf_appendstr(buf, path); + ecs_strbuf_appendlit(buf, "#[reset]"); + ecs_os_free(path); + color_chars = ecs_os_strlen("#[blue]#[reset]"); + } + return color_chars; +} + +char* ecs_rule_str_w_profile( + const ecs_rule_t *rule, + const ecs_iter_t *it) +{ + ecs_poly_assert(rule, ecs_rule_t); + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_rule_op_t *ops = rule->ops; + int32_t i, count = rule->op_count, indent = 0; + + for (i = 0; i < count; i ++) { + ecs_rule_op_t *op = &ops[i]; + ecs_flags16_t flags = op->flags; + ecs_flags16_t src_flags = flecs_rule_ref_flags(flags, EcsRuleSrc); + ecs_flags16_t first_flags = flecs_rule_ref_flags(flags, EcsRuleFirst); + ecs_flags16_t second_flags = flecs_rule_ref_flags(flags, EcsRuleSecond); + + if (it) { +#ifdef FLECS_DEBUG + const ecs_rule_iter_t *rit = &it->priv.iter.rule; + ecs_strbuf_append(&buf, + "#[green]%4d -> #[red]%4d <- #[grey] | ", + rit->profile[i].count[0], + rit->profile[i].count[1]); +#endif + } + + ecs_strbuf_append(&buf, + "#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ", + i, op->prev, op->next); + int32_t hidden_chars, start = ecs_strbuf_written(&buf); + if (op->kind == EcsRuleEnd) { + indent --; + } + + ecs_strbuf_append(&buf, "%*s", indent, ""); + ecs_strbuf_appendstr(&buf, flecs_rule_op_str(op->kind)); + ecs_strbuf_appendstr(&buf, " "); + + int32_t written = ecs_strbuf_written(&buf); + for (int32_t j = 0; j < (12 - (written - start)); j ++) { + ecs_strbuf_appendch(&buf, ' '); + } + + hidden_chars = flecs_rule_op_ref_str(rule, &op->src, src_flags, &buf); + + if (op->kind == EcsRuleNot || + op->kind == EcsRuleOr || + op->kind == EcsRuleOptional || + op->kind == EcsRuleIf) + { + indent ++; + } + + if (!first_flags && !second_flags) { + ecs_strbuf_appendstr(&buf, "\n"); + continue; + } + + written = ecs_strbuf_written(&buf) - hidden_chars; + for (int32_t j = 0; j < (30 - (written - start)); j ++) { + ecs_strbuf_appendch(&buf, ' '); + } + + if (!first_flags && !second_flags) { + ecs_strbuf_appendstr(&buf, "\n"); + continue; + } + + ecs_strbuf_appendstr(&buf, "("); + flecs_rule_op_ref_str(rule, &op->first, first_flags, &buf); + + if (second_flags) { + ecs_strbuf_appendstr(&buf, ", "); + flecs_rule_op_ref_str(rule, &op->second, second_flags, &buf); + } else { + switch (op->kind) { + case EcsRulePredEqName: + case EcsRulePredNeqName: + case EcsRulePredEqMatch: + case EcsRulePredNeqMatch: { + int8_t term_index = op->term_index; + ecs_strbuf_appendstr(&buf, ", #[yellow]\""); + ecs_strbuf_appendstr(&buf, rule->filter.terms[term_index].second.name); + ecs_strbuf_appendstr(&buf, "\"#[reset]"); + break; + } + case EcsRuleLookup: { + ecs_var_id_t src_id = op->src.var; + ecs_strbuf_appendstr(&buf, ", #[yellow]\""); + ecs_strbuf_appendstr(&buf, rule->vars[src_id].lookup); + ecs_strbuf_appendstr(&buf, "\"#[reset]"); + break; + } + default: + break; + } + } + + ecs_strbuf_appendch(&buf, ')'); + + ecs_strbuf_appendch(&buf, '\n'); + } + +#ifdef FLECS_LOG + char *str = ecs_strbuf_get(&buf); + flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); + ecs_os_free(str); +#endif + return ecs_strbuf_get(&buf); +} + +char* ecs_rule_str( + const ecs_rule_t *rule) +{ + return ecs_rule_str_w_profile(rule, NULL); +} + +const ecs_filter_t* ecs_rule_get_filter( + const ecs_rule_t *rule) +{ + return &rule->filter; +} + +const char* ecs_rule_parse_vars( + ecs_rule_t *rule, + ecs_iter_t *it, + const char *expr) +{ + ecs_poly_assert(rule, ecs_rule_t); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL) + char token[ECS_MAX_TOKEN_SIZE]; + const char *ptr = expr; + bool paren = false; + + const char *name = NULL; + if (rule->filter.entity) { + name = ecs_get_name(rule->filter.world, rule->filter.entity); + } + + ptr = ecs_parse_ws_eol(ptr); + if (!ptr[0]) { + return ptr; + } + + if (ptr[0] == '(') { + paren = true; + ptr = ecs_parse_ws_eol(ptr + 1); + if (ptr[0] == ')') { + return ptr + 1; + } + } + + do { + ptr = ecs_parse_ws_eol(ptr); + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } + + int var = ecs_rule_find_var(rule, token); + if (var == -1) { + ecs_parser_error(name, expr, (ptr - expr), + "unknown variable '%s'", token); + return NULL; + } + + ptr = ecs_parse_ws_eol(ptr); + if (ptr[0] != ':') { + ecs_parser_error(name, expr, (ptr - expr), + "missing ':'"); + return NULL; + } + + ptr = ecs_parse_ws_eol(ptr + 1); + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } + + ecs_entity_t val = ecs_lookup_fullpath(rule->filter.world, token); + if (!val) { + ecs_parser_error(name, expr, (ptr - expr), + "unresolved entity '%s'", token); + return NULL; + } + + ecs_iter_set_var(it, var, val); + + ptr = ecs_parse_ws_eol(ptr); + if (ptr[0] == ')') { + if (!paren) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected closing parenthesis"); + return NULL; + } + + ptr ++; + break; + } else if (ptr[0] == ',') { + ptr ++; + } else if (!ptr[0]) { + if (paren) { + ecs_parser_error(name, expr, (ptr - expr), + "missing closing parenthesis"); + return NULL; + } + break; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected , or end of string"); + return NULL; + } + } while (true); + + return ptr; +error: + return NULL; +} + +#endif + +/** + * @file addons/rules/compile.c + * @brief Compile rule program from filter. + */ + + +#ifdef FLECS_RULES + +#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ + +ecs_rule_lbl_t flecs_itolbl(int64_t val) { + return flecs_ito(int16_t, val); +} + +static +ecs_var_id_t flecs_itovar(int64_t val) { + return flecs_ito(uint8_t, val); +} + +static +ecs_var_id_t flecs_utovar(uint64_t val) { + return flecs_uto(uint8_t, val); +} + +#ifdef FLECS_DEBUG +#define flecs_set_var_label(var, lbl) (var)->label = lbl +#else +#define flecs_set_var_label(var, lbl) +#endif + +static +bool flecs_rule_is_builtin_pred( + ecs_term_t *term) +{ + if (term->first.flags & EcsIsEntity) { + ecs_entity_t id = term->first.id; + if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { + return true; + } + } + return false; +} + +bool flecs_rule_is_written( + ecs_var_id_t var_id, + uint64_t written) +{ + if (var_id == EcsVarNone) { + return true; + } + + ecs_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); + return (written & (1ull << var_id)) != 0; +} + +static +void flecs_rule_write( + ecs_var_id_t var_id, + uint64_t *written) +{ + ecs_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); + *written |= (1ull << var_id); +} + +static +void flecs_rule_write_ctx( + ecs_var_id_t var_id, + ecs_rule_compile_ctx_t *ctx, + bool cond_write) +{ + bool is_written = flecs_rule_is_written(var_id, ctx->written); + flecs_rule_write(var_id, &ctx->written); + if (!is_written) { + if (cond_write) { + flecs_rule_write(var_id, &ctx->cond_written); + } + } +} + +ecs_flags16_t flecs_rule_ref_flags( + ecs_flags16_t flags, + ecs_flags16_t kind) +{ + return (flags >> kind) & (EcsRuleIsVar | EcsRuleIsEntity); +} + +bool flecs_ref_is_written( + const ecs_rule_op_t *op, + const ecs_rule_ref_t *ref, + ecs_flags16_t kind, + uint64_t written) +{ + ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, kind); + if (flags & EcsRuleIsEntity) { + ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); + if (ref->entity) { + return true; + } + } else if (flags & EcsRuleIsVar) { + return flecs_rule_is_written(ref->var, written); + } + return false; +} + +static +bool flecs_rule_var_is_anonymous( + const ecs_rule_t *rule, + ecs_var_id_t var_id) +{ + ecs_rule_var_t *var = &rule->vars[var_id]; + return var->anonymous; +} + +static +ecs_var_id_t flecs_rule_find_var_id( + const ecs_rule_t *rule, + const char *name, + ecs_var_kind_t kind) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Backwards compatibility */ + if (!ecs_os_strcmp(name, "This")) { + name = "this"; + } + + if (kind == EcsVarTable) { + if (!ecs_os_strcmp(name, EcsThisName)) { + if (rule->has_table_this) { + return 0; + } else { + return EcsVarNone; + } + } + + if (!flecs_name_index_is_init(&rule->tvar_index)) { + return EcsVarNone; + } + + uint64_t index = flecs_name_index_find( + &rule->tvar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; + } + return flecs_utovar(index); + } + + if (kind == EcsVarEntity) { + if (!flecs_name_index_is_init(&rule->evar_index)) { + return EcsVarNone; + } + + uint64_t index = flecs_name_index_find( + &rule->evar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; + } + return flecs_utovar(index); + } + + ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); + + /* If searching for any kind of variable, start with most specific */ + ecs_var_id_t index = flecs_rule_find_var_id(rule, name, EcsVarEntity); + if (index != EcsVarNone) { + return index; + } + + return flecs_rule_find_var_id(rule, name, EcsVarTable); +} + +int32_t ecs_rule_var_count( + const ecs_rule_t *rule) +{ + return rule->var_pub_count; +} + +int32_t ecs_rule_find_var( + const ecs_rule_t *rule, + const char *name) +{ + ecs_var_id_t var_id = flecs_rule_find_var_id(rule, name, EcsVarEntity); + if (var_id == EcsVarNone) { + if (rule->filter.flags & EcsFilterMatchThis) { + if (!ecs_os_strcmp(name, "This")) { + name = "this"; + } + if (!ecs_os_strcmp(name, EcsThisName)) { + var_id = 0; + } + } + if (var_id == EcsVarNone) { + return -1; + } + } + return (int32_t)var_id; +} + +const char* ecs_rule_var_name( + const ecs_rule_t *rule, + int32_t var_id) +{ + if (var_id) { + return rule->vars[var_id].name; + } else { + return EcsThisName; + } +} + +bool ecs_rule_var_is_entity( + const ecs_rule_t *rule, + int32_t var_id) +{ + return rule->vars[var_id].kind == EcsVarEntity; +} + +static +const char* flecs_term_id_var_name( + ecs_term_id_t *term_id) +{ + if (!(term_id->flags & EcsIsVariable)) { + return NULL; + } + + if (term_id->id == EcsThis) { + return EcsThisName; + } + + return term_id->name; +} + +static +bool flecs_term_id_is_wildcard( + ecs_term_id_t *term_id) +{ + if ((term_id->flags & EcsIsVariable) && + ((term_id->id == EcsWildcard) || (term_id->id == EcsAny))) + { + return true; + } + return false; +} + +static +ecs_var_id_t flecs_rule_add_var( + ecs_rule_t *rule, + const char *name, + ecs_vec_t *vars, + ecs_var_kind_t kind) +{ + const char *dot = NULL; + if (name) { + dot = strchr(name, '.'); + if (dot) { + kind = EcsVarEntity; /* lookup variables are always entities */ + } + } + + ecs_hashmap_t *var_index = NULL; + ecs_var_id_t var_id = EcsVarNone; + if (name) { + if (kind == EcsVarAny) { + var_id = flecs_rule_find_var_id(rule, name, EcsVarEntity); + if (var_id != EcsVarNone) { + return var_id; + } + + var_id = flecs_rule_find_var_id(rule, name, EcsVarTable); + if (var_id != EcsVarNone) { + return var_id; + } + + kind = EcsVarTable; + } else { + var_id = flecs_rule_find_var_id(rule, name, kind); + if (var_id != EcsVarNone) { + return var_id; + } + } + + if (kind == EcsVarTable) { + var_index = &rule->tvar_index; + } else { + var_index = &rule->evar_index; + } + + /* If we're creating an entity var, check if it has a table variant */ + if (kind == EcsVarEntity && var_id == EcsVarNone) { + var_id = flecs_rule_find_var_id(rule, name, EcsVarTable); + } + } + + ecs_rule_var_t *var; + ecs_var_id_t result; + if (vars) { + var = ecs_vec_append_t(NULL, vars, ecs_rule_var_t); + result = var->id = flecs_itovar(ecs_vec_count(vars)); + } else { + ecs_dbg_assert(rule->var_count < rule->var_size, + ECS_INTERNAL_ERROR, NULL); + var = &rule->vars[rule->var_count]; + result = var->id = flecs_itovar(rule->var_count); + rule->var_count ++; + } + + var->kind = flecs_ito(int8_t, kind); + var->name = name; + var->table_id = var_id; + var->base_id = 0; + var->lookup = NULL; + flecs_set_var_label(var, NULL); + + if (name) { + flecs_name_index_init_if(var_index, NULL); + flecs_name_index_ensure(var_index, var->id, name, 0, 0); + var->anonymous = name[0] == '_'; + + /* Handle variables that require a by-name lookup, e.g. $this.wheel */ + if (dot != NULL) { + ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); + var->lookup = dot + 1; + } + } + + return result; +} + +static +ecs_var_id_t flecs_rule_add_var_for_term_id( + ecs_rule_t *rule, + ecs_term_id_t *term_id, + ecs_vec_t *vars, + ecs_var_kind_t kind) +{ + const char *name = flecs_term_id_var_name(term_id); + if (!name) { + return EcsVarNone; + } + + return flecs_rule_add_var(rule, name, vars, kind); +} + +/* This function walks over terms to discover which variables are used in the + * query. It needs to provide the following functionality: + * - create table vars for all variables used as source + * - create entity vars for all variables not used as source + * - create entity vars for all non-$this vars + * - create anonymous vars to store the content of wildcards + * - create anonymous vars to store result of lookups (for $var.child_name) + * - create anonymous vars for resolving component inheritance + * - create array that stores the source variable for each field + * - ensure table vars for non-$this variables are anonymous + * - ensure variables created inside scopes are anonymous + * - place anonymous variables after public variables in vars array + */ +static +int flecs_rule_discover_vars( + ecs_stage_t *stage, + ecs_rule_t *rule) +{ + ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ + ecs_vec_reset_t(NULL, vars, ecs_rule_var_t); + + ecs_term_t *terms = rule->filter.terms; + int32_t a, i, anonymous_count = 0, count = rule->filter.term_count; + int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; + bool table_this = false, entity_before_table_this = false; + + /* For This table lookups during discovery. This will be overwritten after + * discovery with whether the rule actually has a This table variable. */ + rule->has_table_this = true; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *first = &term->first; + ecs_term_id_t *second = &term->second; + ecs_term_id_t *src = &term->src; + + if (first->id == EcsScopeOpen) { + /* Keep track of which variables are first used in scope, so that we + * can mark them as anonymous. Terms inside a scope are collapsed + * into a single result, which means that outside of the scope the + * value of those variables is undefined. */ + if (!scope) { + scoped_var_index = ecs_vec_count(vars); + } + scope ++; + continue; + } else if (first->id == EcsScopeClose) { + if (!--scope) { + /* Any new variables declared after entering a scope should be + * marked as anonymous. */ + int32_t v; + for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { + ecs_vec_get_t(vars, ecs_rule_var_t, v)->anonymous = true; + } + } + continue; + } + + ecs_var_id_t first_var_id = flecs_rule_add_var_for_term_id( + rule, first, vars, EcsVarEntity); + if (first_var_id == EcsVarNone) { + /* If first is not a variable, check if we need to insert anonymous + * variable for resolving component inheritance */ + if (term->flags & EcsTermIdInherited) { + anonymous_count += 2; /* table & entity variable */ + } + + /* If first is a wildcard, insert anonymous variable */ + if (flecs_term_id_is_wildcard(first)) { + anonymous_count ++; + } + } + + if ((src->flags & EcsIsVariable) && (src->id != EcsThis)) { + const char *var_name = flecs_term_id_var_name(src); + if (var_name) { + ecs_var_id_t var_id = flecs_rule_find_var_id( + rule, var_name, EcsVarEntity); + if (var_id == EcsVarNone || var_id == first_var_id) { + var_id = flecs_rule_add_var( + rule, var_name, vars, EcsVarEntity); + } + + if (var_id != EcsVarNone) { + /* Mark variable as one for which we need to create a table + * variable. Don't create table variable now, so that we can + * store it in the non-public part of the variable array. */ + ecs_rule_var_t *var = ecs_vec_get_t( + vars, ecs_rule_var_t, (int32_t)var_id - 1); + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + if (!var->lookup) { + var->kind = EcsVarAny; + anonymous_table_count ++; + } + + if (!(term->flags & EcsTermNoData)) { + /* Can't have an anonymous variable as source of a term + * that returns a component. We need to return each + * instance of the component, whereas anonymous + * variables are not guaranteed to be resolved to + * individual entities. */ + if (var->anonymous) { + ecs_err( + "can't use anonymous variable '%s' as source of " + "data term", var->name); + goto error; + } + } + + /* Track which variable ids are used as field source */ + if (!rule->src_vars) { + rule->src_vars = ecs_os_calloc_n(ecs_var_id_t, + rule->filter.field_count); + } + + rule->src_vars[term->field_index] = var_id; + } + } else { + if (flecs_term_id_is_wildcard(src)) { + anonymous_count ++; + } + } + } else if ((src->flags & EcsIsVariable) && (src->id == EcsThis)) { + if (flecs_rule_is_builtin_pred(term) && term->oper == EcsOr) { + flecs_rule_add_var(rule, EcsThisName, vars, EcsVarEntity); + } + } + + if (flecs_rule_add_var_for_term_id( + rule, second, vars, EcsVarEntity) == EcsVarNone) + { + /* If second is a wildcard, insert anonymous variable */ + if (flecs_term_id_is_wildcard(second)) { + anonymous_count ++; + } + } + + if (src->flags & EcsIsVariable && second->flags & EcsIsVariable) { + if (term->flags & EcsTermTransitive) { + /* Anonymous variable to store temporary id for finding + * targets for transitive relationship, see compile_term. */ + anonymous_count ++; + } + } + + /* Track if a This entity variable is used before a potential This table + * variable. If this happens, the rule has no This table variable */ + if (src->id == EcsThis) { + table_this = true; + } + if (first->id == EcsThis || second->id == EcsThis) { + if (!table_this) { + entity_before_table_this = true; + } + } + } + + int32_t var_count = ecs_vec_count(vars); + ecs_var_id_t placeholder = EcsVarNone - 1; + bool replace_placeholders = false; + + /* Ensure lookup variables have table and/or entity variables */ + for (i = 0; i < var_count; i ++) { + ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); + if (var->lookup) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; + + ecs_var_id_t base_table_id = flecs_rule_find_var_id( + rule, var_name, EcsVarTable); + if (base_table_id != EcsVarNone) { + var->table_id = base_table_id; + } else if (anonymous_table_count) { + /* Scan for implicit anonymous table variables that haven't been + * inserted yet (happens after this step). Doing this here vs. + * ensures that anonymous variables are appended at the end of + * the variable array, while also ensuring that variable ids are + * stable (no swapping of table var ids that are in use). */ + for (a = 0; a < var_count; a ++) { + ecs_rule_var_t *avar = ecs_vec_get_t( + vars, ecs_rule_var_t, a); + if (avar->kind == EcsVarAny) { + if (!ecs_os_strcmp(avar->name, var_name)) { + base_table_id = (ecs_var_id_t)(a + 1); + break; + } + } + } + if (base_table_id != EcsVarNone) { + /* Set marker so we can set the new table id afterwards */ + var->table_id = placeholder; + replace_placeholders = true; + } + } + + ecs_var_id_t base_entity_id = flecs_rule_find_var_id( + rule, var_name, EcsVarEntity); + if (base_entity_id == EcsVarNone) { + /* Get name from table var (must exist). We can't use allocated + * name since variables don't own names. */ + const char *base_name = NULL; + if (base_table_id) { + ecs_rule_var_t *base_table_var = ecs_vec_get_t( + vars, ecs_rule_var_t, (int32_t)base_table_id - 1); + base_name = base_table_var->name; + } else { + base_name = EcsThisName; + } + + base_entity_id = flecs_rule_add_var( + rule, base_name, vars, EcsVarEntity); + var = ecs_vec_get_t(vars, ecs_rule_var_t, i); + } + + var->base_id = base_entity_id; + + ecs_os_free(var_name); + } + } + var_count = ecs_vec_count(vars); + + /* Add non-This table variables */ + if (anonymous_table_count) { + anonymous_table_count = 0; + for (i = 0; i < var_count; i ++) { + ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); + if (var->kind == EcsVarAny) { + var->kind = EcsVarEntity; + + ecs_var_id_t var_id = flecs_rule_add_var( + rule, var->name, vars, EcsVarTable); + ecs_vec_get_t(vars, ecs_rule_var_t, i)->table_id = var_id; + anonymous_table_count ++; + } + } + + var_count = ecs_vec_count(vars); + } + + /* If any forward references to newly added anonymous tables exist, replace + * them with the actual table variable ids. */ + if (replace_placeholders) { + for (i = 0; i < var_count; i ++) { + ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); + if (var->table_id == placeholder) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; + + var->table_id = flecs_rule_find_var_id( + rule, var_name, EcsVarTable); + ecs_assert(var->table_id != EcsVarNone, + ECS_INTERNAL_ERROR, NULL); + + ecs_os_free(var_name); + } + } + } + + /* Always include spot for This variable, even if rule doesn't use it */ + var_count ++; + + ecs_rule_var_t *rule_vars = &rule->vars_cache.var; + if ((var_count + anonymous_count) > 1) { + rule_vars = ecs_os_malloc( + (ECS_SIZEOF(ecs_rule_var_t) + ECS_SIZEOF(char*)) * + (var_count + anonymous_count)); + } + + rule->vars = rule_vars; + rule->var_count = var_count; + rule->var_pub_count = var_count; + rule->has_table_this = !entity_before_table_this; + +#ifdef FLECS_DEBUG + rule->var_size = var_count + anonymous_count; +#endif + + char **var_names = ECS_ELEM(rule_vars, ECS_SIZEOF(ecs_rule_var_t), + var_count + anonymous_count); + rule->var_names = (char**)var_names; + + rule_vars[0].kind = EcsVarTable; + rule_vars[0].name = NULL; + flecs_set_var_label(&rule_vars[0], NULL); + rule_vars[0].id = 0; + rule_vars[0].table_id = EcsVarNone; + rule_vars[0].lookup = NULL; + var_names[0] = ECS_CONST_CAST(char*, rule_vars[0].name); + rule_vars ++; + var_names ++; + var_count --; + + if (var_count) { + ecs_rule_var_t *user_vars = ecs_vec_first_t(vars, ecs_rule_var_t); + ecs_os_memcpy_n(rule_vars, user_vars, ecs_rule_var_t, var_count); + for (i = 0; i < var_count; i ++) { + var_names[i] = ECS_CONST_CAST(char*, rule_vars[i].name); + } + } + + /* Hide anonymous table variables from application */ + rule->var_pub_count -= anonymous_table_count; + + /* Sanity check to make sure that the public part of the variable array only + * contains entity variables. */ +#ifdef FLECS_DEBUG + for (i = 1 /* first element = $this */; i < rule->var_pub_count; i ++) { + ecs_assert(rule->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); + } +#endif + + return 0; +error: + return -1; +} + +static +ecs_var_id_t flecs_rule_most_specific_var( + ecs_rule_t *rule, + const char *name, + ecs_var_kind_t kind, + ecs_rule_compile_ctx_t *ctx) +{ + if (kind == EcsVarTable || kind == EcsVarEntity) { + return flecs_rule_find_var_id(rule, name, kind); + } + + ecs_var_id_t evar = flecs_rule_find_var_id(rule, name, EcsVarEntity); + if ((evar != EcsVarNone) && flecs_rule_is_written(evar, ctx->written)) { + /* If entity variable is available and written to, it contains the most + * specific result and should be used. */ + return evar; + } + + ecs_var_id_t tvar = flecs_rule_find_var_id(rule, name, EcsVarTable); + if ((tvar != EcsVarNone) && !flecs_rule_is_written(tvar, ctx->written)) { + /* If variable of any kind is requested and variable hasn't been written + * yet, write to table variable */ + return tvar; + } + + /* If table var is written, and entity var doesn't exist or is not written, + * return table var */ + if (tvar != EcsVarNone) { + return tvar; + } else { + return evar; + } +} + +static +ecs_rule_lbl_t flecs_rule_op_insert( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_rule_op_t); + int32_t count = ecs_vec_count(ctx->ops); + *elem = *op; + if (count > 1) { + if (ctx->cur->lbl_begin == -1) { + /* Variables written by previous instruction can't be written by + * this instruction, except when this is a union. */ + elem->written &= ~elem[-1].written; + } + } + + elem->next = flecs_itolbl(count); + elem->prev = flecs_itolbl(count - 2); + return flecs_itolbl(count - 1); +} + +static +ecs_rule_op_t* flecs_rule_begin_block( + ecs_rule_op_kind_t kind, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t op = {0}; + op.kind = flecs_ito(uint8_t, kind); + ctx->cur->lbl_begin = flecs_rule_op_insert(&op, ctx); + return ecs_vec_get_t(ctx->ops, ecs_rule_op_t, ctx->cur->lbl_begin); +} + +static +void flecs_rule_end_block( + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t new_op = {0}; + new_op.kind = EcsRuleEnd; + ecs_rule_lbl_t end = flecs_rule_op_insert(&new_op, ctx); + + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + ops[ctx->cur->lbl_begin].next = end; + + ecs_rule_op_t *end_op = &ops[end]; + if (ctx->cur->lbl_query != -1) { + ecs_rule_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op->prev = ctx->cur->lbl_begin; + end_op->src = query_op->src; + end_op->first = query_op->first; + end_op->second = query_op->second; + end_op->flags = query_op->flags; + end_op->field_index = query_op->field_index; + } else { + end_op->prev = ctx->cur->lbl_begin; + end_op->field_index = -1; + } + + ctx->cur->lbl_begin = -1; +} + +static +void flecs_rule_begin_block_cond_eval( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx, + ecs_write_flags_t cond_write_state) +{ + ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; + ecs_write_flags_t cond_mask = 0; + + if (flecs_rule_ref_flags(op->flags, EcsRuleFirst) == EcsRuleIsVar) { + first_var = op->first.var; + ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << first_var); + } + if (flecs_rule_ref_flags(op->flags, EcsRuleSecond) == EcsRuleIsVar) { + second_var = op->second.var; + ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << second_var); + } + if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) == EcsRuleIsVar) { + src_var = op->src.var; + ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << src_var); + } + + /* Variables set in an OR chain are marked as conditional writes. However, + * writes from previous terms in the current OR chain shouldn't be treated + * as variables that are conditionally set, so instead use the write mask + * from before the chain started. */ + if (ctx->ctrlflow->in_or) { + cond_write_state = ctx->ctrlflow->cond_written_or; + } + + /* If this term uses conditionally set variables, insert instruction that + * jumps over the term if the variables weren't set yet. */ + if (cond_mask & cond_write_state) { + ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); + + ecs_rule_op_t jmp_op = {0}; + jmp_op.kind = EcsRuleIf; + + if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { + jmp_op.flags |= (EcsRuleIsVar << EcsRuleFirst); + jmp_op.first.var = first_var; + } + if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { + jmp_op.flags |= (EcsRuleIsVar << EcsRuleSecond); + jmp_op.second.var = second_var; + } + if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { + jmp_op.flags |= (EcsRuleIsVar << EcsRuleSrc); + jmp_op.src.var = src_var; + } + + flecs_rule_op_insert(&jmp_op, ctx); + } else { + ctx->cur->lbl_cond_eval = -1; + } +} + +static +void flecs_rule_end_block_cond_eval( + ecs_rule_compile_ctx_t *ctx) +{ + if (ctx->cur->lbl_cond_eval == -1) { + return; + } + + ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_rule_op_t end_op = {0}; + end_op.kind = EcsRuleEnd; + ecs_rule_lbl_t end = flecs_rule_op_insert(&end_op, ctx); + + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + ops[ctx->cur->lbl_cond_eval].next = end; + + ecs_rule_op_t *end_op_ptr = &ops[end]; + ecs_rule_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op_ptr->prev = ctx->cur->lbl_cond_eval; + end_op_ptr->src = query_op->src; + end_op_ptr->first = query_op->first; + end_op_ptr->second = query_op->second; + end_op_ptr->flags = query_op->flags; + end_op_ptr->field_index = query_op->field_index; +} + +static +void flecs_rule_begin_block_or( + ecs_rule_op_t *op, + ecs_term_t *term, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t *or_op = flecs_rule_begin_block(EcsRuleNot, ctx); + or_op->kind = EcsRuleOr; + + /* Set the source of the evaluate terms as source of the Or instruction. + * This lets the engine determine whether the variable has already been + * written. When the source is not yet written, an OR operation needs to + * take the union of all the terms in the OR chain. When the variable is + * known, it will return after the first matching term. + * + * In case a term in the OR expression is an equality predicate which + * compares the left hand side with a variable, the variable acts as an + * alias, so we can always assume that it's written. */ + bool add_src = true; + if (term->first.id == EcsPredEq && term->second.flags & EcsIsVariable) { + if (!(flecs_rule_is_written(op->src.var, ctx->written))) { + add_src = false; + } + } + + if (add_src) { + if (op->flags & (EcsRuleIsVar << EcsRuleSrc)) { + or_op->flags = (EcsRuleIsVar << EcsRuleSrc); + or_op->src = op->src; + } + } +} + +static +void flecs_rule_end_block_or( + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t op = {0}; + op.kind = EcsRuleEnd; + ecs_rule_lbl_t end = flecs_rule_op_insert(&op, ctx); + + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + int32_t i, j, prev_or = -2; + for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { + if (ops[i].next == FlecsRuleOrMarker) { + if (prev_or != -2) { + ops[prev_or].prev = flecs_itolbl(i); + } + ops[i].next = flecs_itolbl(end); + prev_or = i; + } else { + /* Combine operation with next OR marker. This supports OR chains + * with terms that require multiple operations to test. */ + for (j = i + 1; j < end; j ++) { + if (ops[j].next == FlecsRuleOrMarker) { + if (j == (end - 1)) { + ops[i].prev = ctx->cur->lbl_begin; + } else { + ops[i].prev = flecs_itolbl(j + 1); + } + break; + } + } + } + } + + ops[ctx->cur->lbl_begin].next = flecs_itolbl(end); + ops[end].prev = ctx->cur->lbl_begin; + ops[end - 1].prev = ctx->cur->lbl_begin; + + /* Scan which variables were conditionally written in the OR chain and + * reset instructions after the OR chain. If a variable is set in part one + * of a chain but not part two, there would be nothing writing to the + * variable in part two, leaving it to the previous value. To address this + * a reset is inserted that resets the variable value on redo. */ + for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { + ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); + ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); + + if (!prev && cur) { + ecs_rule_op_t reset_op = {0}; + reset_op.kind = EcsRuleReset; + reset_op.flags |= (EcsRuleIsVar << EcsRuleSrc); + reset_op.src.var = flecs_itovar(i); + flecs_rule_op_insert(&reset_op, ctx); + } + } + + ctx->ctrlflow->in_or = false; + ctx->cur->lbl_begin = -1; +} + +static +void flecs_rule_insert_each( + ecs_var_id_t tvar, + ecs_var_id_t evar, + ecs_rule_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_rule_op_t each = {0}; + each.kind = EcsRuleEach; + each.src.var = evar; + each.first.var = tvar; + each.flags = (EcsRuleIsVar << EcsRuleSrc) | + (EcsRuleIsVar << EcsRuleFirst); + flecs_rule_write_ctx(evar, ctx, cond_write); + flecs_rule_write(evar, &each.written); + flecs_rule_op_insert(&each, ctx); +} + +static +void flecs_rule_insert_lookup( + ecs_var_id_t base_var, + ecs_var_id_t evar, + ecs_rule_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_rule_op_t lookup = {0}; + lookup.kind = EcsRuleLookup; + lookup.src.var = evar; + lookup.first.var = base_var; + lookup.flags = (EcsRuleIsVar << EcsRuleSrc) | + (EcsRuleIsVar << EcsRuleFirst); + flecs_rule_write_ctx(evar, ctx, cond_write); + flecs_rule_write(evar, &lookup.written); + flecs_rule_op_insert(&lookup, ctx); +} + +static +void flecs_rule_insert_unconstrained_transitive( + ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx, + bool cond_write) +{ + /* Create anonymous variable to store the target ids. This will return the + * list of targets without constraining the variable of the term, which + * needs to stay variable to find all transitive relationships for a src. */ + ecs_var_id_t tgt = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); + flecs_set_var_label(&rule->vars[tgt], rule->vars[op->second.var].name); + + /* First, find ids to start traversal from. This fixes op.second. */ + ecs_rule_op_t find_ids = {0}; + find_ids.kind = EcsRuleIdsRight; + find_ids.field_index = -1; + find_ids.first = op->first; + find_ids.second = op->second; + find_ids.flags = op->flags; + find_ids.flags &= (ecs_flags8_t)~((EcsRuleIsVar|EcsRuleIsEntity) << EcsRuleSrc); + find_ids.second.var = tgt; + flecs_rule_write_ctx(tgt, ctx, cond_write); + flecs_rule_write(tgt, &find_ids.written); + flecs_rule_op_insert(&find_ids, ctx); + + /* Next, iterate all tables for the ids. This fixes op.src */ + ecs_rule_op_t and_op = {0}; + and_op.kind = EcsRuleAnd; + and_op.field_index = op->field_index; + and_op.first = op->first; + and_op.second = op->second; + and_op.src = op->src; + and_op.flags = op->flags | EcsRuleIsSelf; + and_op.second.var = tgt; + flecs_rule_write_ctx(and_op.src.var, ctx, cond_write); + flecs_rule_write(and_op.src.var, &and_op.written); + flecs_rule_op_insert(&and_op, ctx); +} + +static +void flecs_rule_insert_inheritance( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx, + bool cond_write) +{ + /* Anonymous variable to store the resolved component ids */ + ecs_var_id_t tvar = flecs_rule_add_var(rule, NULL, NULL, EcsVarTable); + ecs_var_id_t evar = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); + flecs_set_var_label(&rule->vars[tvar], ecs_get_name(rule->filter.world, term->first.id)); + flecs_set_var_label(&rule->vars[evar], ecs_get_name(rule->filter.world, term->first.id)); + + ecs_rule_op_t trav_op = {0}; + trav_op.kind = EcsRuleTrav; + trav_op.field_index = -1; + trav_op.first.entity = term->first.trav; + trav_op.second.entity = term->first.id; + trav_op.src.var = tvar; + trav_op.flags = EcsRuleIsSelf; + trav_op.flags |= (EcsRuleIsEntity << EcsRuleFirst); + trav_op.flags |= (EcsRuleIsEntity << EcsRuleSecond); + trav_op.flags |= (EcsRuleIsVar << EcsRuleSrc); + trav_op.written |= (1ull << tvar); + if (term->first.flags & EcsSelf) { + trav_op.match_flags |= EcsTermReflexive; + } + flecs_rule_op_insert(&trav_op, ctx); + flecs_rule_insert_each(tvar, evar, ctx, cond_write); + + ecs_rule_ref_t r = { .var = evar }; + op->first = r; + op->flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); + op->flags |= (EcsRuleIsVar << EcsRuleFirst); +} + +static +void flecs_rule_compile_term_id( + ecs_world_t *world, + ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_term_id_t *term_id, + ecs_rule_ref_t *ref, + ecs_flags8_t ref_kind, + ecs_var_kind_t kind, + ecs_rule_compile_ctx_t *ctx, + bool create_wildcard_vars) +{ + (void)world; + + if (!ecs_term_id_is_set(term_id)) { + return; + } + + if (term_id->flags & EcsIsVariable) { + op->flags |= (ecs_flags8_t)(EcsRuleIsVar << ref_kind); + const char *name = flecs_term_id_var_name(term_id); + if (name) { + ref->var = flecs_rule_most_specific_var(rule, name, kind, ctx); + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } else if (create_wildcard_vars) { + bool is_wildcard = flecs_term_id_is_wildcard(term_id); + if (is_wildcard && (kind == EcsVarAny)) { + ref->var = flecs_rule_add_var(rule, NULL, NULL, EcsVarTable); + } else { + ref->var = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); + } + if (is_wildcard) { + flecs_set_var_label(&rule->vars[ref->var], + ecs_get_name(world, term_id->id)); + } + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } + } + + if (term_id->flags & EcsIsEntity) { + op->flags |= (ecs_flags8_t)(EcsRuleIsEntity << ref_kind); + ref->entity = term_id->id; + } +} + +static +int flecs_rule_compile_ensure_vars( + ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_rule_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_rule_compile_ctx_t *ctx, + bool cond_write, + bool *written_out) +{ + ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); + bool written = false; + + if (flags & EcsRuleIsVar) { + ecs_var_id_t var_id = ref->var; + ecs_rule_var_t *var = &rule->vars[var_id]; + + if (var->kind == EcsVarEntity && + !flecs_rule_is_written(var_id, ctx->written)) + { + /* If entity variable is not yet written but a table variant exists + * that has been written, insert each operation to translate from + * entity variable to table */ + ecs_var_id_t tvar = var->table_id; + if ((tvar != EcsVarNone) && + flecs_rule_is_written(tvar, ctx->written)) + { + if (var->lookup) { + if (!flecs_rule_is_written(tvar, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } + + if (!flecs_rule_is_written(var->base_id, ctx->written)) { + flecs_rule_insert_each( + tvar, var->base_id, ctx, cond_write); + } + } else { + flecs_rule_insert_each(tvar, var_id, ctx, cond_write); + } + + /* Variable was written, just not as entity */ + written = true; + } else if (var->lookup) { + if (!flecs_rule_is_written(var->base_id, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } + } + } + + written |= flecs_rule_is_written(var_id, ctx->written); + } else { + /* If it's not a variable, it's always written */ + written = true; + } + + if (written_out) { + *written_out = written; + } + + return 0; +} + +static +bool flecs_rule_compile_lookup( + ecs_rule_t *rule, + ecs_var_id_t var_id, + ecs_rule_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_rule_var_t *var = &rule->vars[var_id]; + if (var->lookup) { + flecs_rule_insert_lookup(var->base_id, var_id, ctx, cond_write); + return true; + } else { + return false; + } +} + +static +void flecs_rule_insert_contains( + ecs_rule_t *rule, + ecs_var_id_t src_var, + ecs_var_id_t other_var, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t contains = {0}; + if ((src_var != other_var) && (src_var == rule->vars[other_var].table_id)) { + contains.kind = EcsRuleContain; + contains.src.var = src_var; + contains.first.var = other_var; + contains.flags |= (EcsRuleIsVar << EcsRuleSrc) | + (EcsRuleIsVar << EcsRuleFirst); + flecs_rule_op_insert(&contains, ctx); + } +} + +static +void flecs_rule_insert_pair_eq( + int32_t field_index, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t contains = {0}; + contains.kind = EcsRulePairEq; + contains.field_index = flecs_ito(int8_t, field_index); + flecs_rule_op_insert(&contains, ctx); +} + +static +bool flecs_rule_term_fixed_id( + ecs_filter_t *filter, + ecs_term_t *term) +{ + /* Transitive/inherited terms have variable ids */ + if (term->flags & (EcsTermTransitive|EcsTermIdInherited)) { + return false; + } + + /* Or terms can match different ids */ + if (term->oper == EcsOr) { + return false; + } + if ((term != filter->terms) && term[-1].oper == EcsOr) { + return false; + } + + /* Wildcards can assume different ids */ + if (ecs_id_is_wildcard(term->id)) { + return false; + } + + /* Any terms can have fixed ids, but they require special handling */ + if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + return false; + } + + /* First terms that are Not or Optional require special handling */ + if (term->oper == EcsNot || term->oper == EcsOptional) { + if (term == filter->terms) { + return false; + } + } + + return true; +} + +static +int flecs_rule_compile_builtin_pred( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_op_t *op, + ecs_write_flags_t write_state) +{ + ecs_entity_t id = term->first.id; + + ecs_rule_op_kind_t eq[] = {EcsRulePredEq, EcsRulePredNeq}; + ecs_rule_op_kind_t eq_name[] = {EcsRulePredEqName, EcsRulePredNeqName}; + ecs_rule_op_kind_t eq_match[] = {EcsRulePredEqMatch, EcsRulePredNeqMatch}; + + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + + if (id == EcsPredEq) { + if (term->second.flags & EcsIsName) { + op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); + } else { + op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); + } + } else if (id == EcsPredMatch) { + op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); + } + + op->first = op->src; + op->src = (ecs_rule_ref_t){0}; + op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleSrc); + op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleFirst); + op->flags |= EcsRuleIsVar << EcsRuleFirst; + + if (flags_2nd & EcsRuleIsVar) { + if (!(write_state & (1ull << op->second.var))) { + ecs_err("uninitialized variable '%s' on right-hand side of " + "equality operator", + ecs_rule_var_name(rule, op->second.var)); + return -1; + } + } + + if (!(write_state & (1ull << op->first.var))) { + /* If this is an == operator with a right-hand side that resolves to a + * single entity, the left-hand side is allowed to be undefined, as the + * instruction will be evaluated as an assignment. */ + if (op->kind != EcsRulePredEq && op->kind != EcsRulePredEqName) { + ecs_err("uninitialized variable '%s' on left-hand side of " + "equality operator", + ecs_rule_var_name(rule, op->first.var)); + return -1; + } + } + + return 0; +} + +static +int flecs_rule_ensure_scope_var( + ecs_rule_t *rule, + ecs_rule_op_t *op, + ecs_rule_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_var_id_t var = ref->var; + + if (rule->vars[var].kind == EcsVarEntity && + !flecs_rule_is_written(var, ctx->written)) + { + ecs_var_id_t table_var = rule->vars[var].table_id; + if (table_var != EcsVarNone && + flecs_rule_is_written(table_var, ctx->written)) + { + if (flecs_rule_compile_ensure_vars( + rule, op, ref, ref_kind, ctx, false, NULL)) + { + goto error; + } + + } + } + + return 0; +error: + return -1; +} + +static +int flecs_rule_ensure_scope_vars( + ecs_world_t *world, + ecs_rule_t *rule, + ecs_rule_compile_ctx_t *ctx, + ecs_term_t *term) +{ + /* If the scope uses variables as entity that have only been written as + * table, resolve them as entities before entering the scope. */ + ecs_term_t *cur = term; + while(cur->first.id != EcsScopeClose) { + /* Dummy operation to obtain variable information for term */ + ecs_rule_op_t op = {0}; + flecs_rule_compile_term_id(world, rule, &op, &cur->first, + &op.first, EcsRuleFirst, EcsVarEntity, ctx, false); + flecs_rule_compile_term_id(world, rule, &op, &cur->second, + &op.second, EcsRuleSecond, EcsVarEntity, ctx, false); + + if (op.flags & (EcsRuleIsVar << EcsRuleFirst)) { + if (flecs_rule_ensure_scope_var( + rule, &op, &op.first, EcsRuleFirst, ctx)) + { + goto error; + } + } + if (op.flags & (EcsRuleIsVar << EcsRuleSecond)) { + if (flecs_rule_ensure_scope_var( + rule, &op, &op.second, EcsRuleSecond, ctx)) + { + goto error; + } + } + + cur ++; + } + + return 0; +error: + return -1; +} + +static +void flecs_rule_compile_push( + ecs_rule_compile_ctx_t *ctx) +{ + ctx->cur = &ctx->ctrlflow[++ ctx->scope]; + ctx->cur->lbl_begin = -1; + ctx->cur->lbl_begin = -1; +} + +static +void flecs_rule_compile_pop( + ecs_rule_compile_ctx_t *ctx) +{ + ctx->cur = &ctx->ctrlflow[-- ctx->scope]; +} + +static +bool flecs_rule_term_is_or( + const ecs_filter_t *filter, + const ecs_term_t *term) +{ + bool first_term = term == filter->terms; + return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); +} + +static +int flecs_rule_compile_term( + ecs_world_t *world, + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_filter_t *filter = &rule->filter; + bool first_term = term == filter->terms; + bool first_is_var = term->first.flags & EcsIsVariable; + bool second_is_var = term->second.flags & EcsIsVariable; + bool src_is_var = term->src.flags & EcsIsVariable; + bool builtin_pred = flecs_rule_is_builtin_pred(term); + bool is_not = (term->oper == EcsNot) && !builtin_pred; + bool is_or = flecs_rule_term_is_or(filter, term); + bool first_or = false, last_or = false; + bool cond_write = term->oper == EcsOptional || is_or; + ecs_rule_op_t op = {0}; + + if (is_or) { + first_or = first_term || (term[-1].oper != EcsOr); + last_or = term->oper != EcsOr; + } + + /* !_ (don't match anything) terms always return nothing. */ + if (is_not && term->id == EcsAny) { + op.kind = EcsRuleNothing; + flecs_rule_op_insert(&op, ctx); + return 0; + } + + if (is_or && (first_term || term[-1].oper != EcsOr)) { + ctx->ctrlflow->cond_written_or = ctx->cond_written; + ctx->ctrlflow->in_or = true; + } + + if (!term->src.id && term->src.flags & EcsIsEntity) { + /* If the term has a 0 source, check if it's a scope open/close */ + if (term->first.id == EcsScopeOpen) { + if (flecs_rule_ensure_scope_vars(world, rule, ctx, term)) { + goto error; + } + if (term->oper == EcsNot) { + ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); + flecs_rule_begin_block(EcsRuleNot, ctx); + } else { + ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + } + flecs_rule_compile_push(ctx); + } else if (term->first.id == EcsScopeClose) { + flecs_rule_compile_pop(ctx); + if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { + ctx->cur->lbl_query = -1; + flecs_rule_end_block(ctx); + } + } else { + /* Noop */ + } + return 0; + } + + if (builtin_pred) { + if (term->second.id == EcsWildcard || term->second.id == EcsAny) { + /* Noop */ + return 0; + } + } + + /* Default instruction for And operators. If the source is fixed (like for + * singletons or terms with an entity source), use With, which like And but + * just matches against a source (vs. finding a source). */ + op.kind = src_is_var ? EcsRuleAnd : EcsRuleWith; + op.field_index = flecs_ito(int8_t, term->field_index); + op.term_index = flecs_ito(int8_t, term - filter->terms); + + /* If rule is transitive, use Trav(ersal) instruction */ + if (term->flags & EcsTermTransitive) { + ecs_assert(ecs_term_id_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); + op.kind = EcsRuleTrav; + } else { + /* Ignore cascade & parent flags */ + ecs_flags32_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsParent); + if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + op.kind = EcsRuleAndAny; + } else if ((term->src.flags & trav_flags) == EcsUp) { + op.kind = EcsRuleUp; + } else if ((term->src.flags & trav_flags) == (EcsSelf|EcsUp)) { + op.kind = EcsRuleSelfUp; + } + } + + /* If term has fixed id, insert simpler instruction that skips dealing with + * wildcard terms and variables */ + if (flecs_rule_term_fixed_id(filter, term)) { + if (op.kind == EcsRuleAnd) { + op.kind = EcsRuleAndId; + } else if (op.kind == EcsRuleSelfUp) { + op.kind = EcsRuleSelfUpId; + } else if (op.kind == EcsRuleUp) { + op.kind = EcsRuleUpId; + } + } + + /* Save write state at start of term so we can use it to reliably track + * variables got written by this term. */ + ecs_write_flags_t cond_write_state = ctx->cond_written; + + /* Resolve component inheritance if necessary */ + ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; + + /* Resolve variables and entities for operation arguments */ + flecs_rule_compile_term_id(world, rule, &op, &term->first, + &op.first, EcsRuleFirst, EcsVarEntity, ctx, true); + flecs_rule_compile_term_id(world, rule, &op, &term->second, + &op.second, EcsRuleSecond, EcsVarEntity, ctx, true); + + if (first_is_var) first_var = op.first.var; + if (second_is_var) second_var = op.second.var; + + flecs_rule_compile_term_id(world, rule, &op, &term->src, + &op.src, EcsRuleSrc, EcsVarAny, ctx, true); + if (src_is_var) src_var = op.src.var; + bool src_written = flecs_rule_is_written(src_var, ctx->written); + + /* Insert each instructions for table -> entity variable if needed */ + bool first_written, second_written; + if (flecs_rule_compile_ensure_vars( + rule, &op, &op.first, EcsRuleFirst, ctx, cond_write, &first_written)) + { + goto error; + } + if (flecs_rule_compile_ensure_vars( + rule, &op, &op.second, EcsRuleSecond, ctx, cond_write, &second_written)) + { + goto error; + } + + /* If the query starts with a Not or Optional term, insert an operation that + * matches all entities. */ + if (first_term && src_is_var && !src_written && term->src.id != EcsAny) { + bool pred_match = builtin_pred && term->first.id == EcsPredMatch; + if (term->oper == EcsNot || term->oper == EcsOptional || pred_match) { + ecs_rule_op_t match_any = {0}; + match_any.kind = EcsAnd; + match_any.flags = EcsRuleIsSelf | (EcsRuleIsEntity << EcsRuleFirst); + match_any.flags |= (EcsRuleIsVar << EcsRuleSrc); + match_any.src = op.src; + match_any.field_index = -1; + if (!pred_match) { + match_any.first.entity = EcsAny; + } else { + /* If matching by name, instead of finding all tables, just find + * the ones with a name. */ + match_any.first.entity = ecs_id(EcsIdentifier); + match_any.second.entity = EcsName; + match_any.flags |= (EcsRuleIsEntity << EcsRuleSecond); + } + match_any.written = (1ull << src_var); + flecs_rule_op_insert(&match_any, ctx); + flecs_rule_write_ctx(op.src.var, ctx, false); + + /* Update write administration */ + src_written = true; + } + } + + /* A bit of special logic for OR expressions and equality predicates. If the + * left-hand of an equality operator is a table, and there are multiple + * operators in an Or expression, the Or chain should match all entities in + * the table that match the right hand sides of the operator expressions. + * For this to work, the src variable needs to be resolved as entity, as an + * Or chain would otherwise only yield the first match from a table. */ + if (src_is_var && src_written && builtin_pred && term->oper == EcsOr) { + /* Or terms are required to have the same source, so we don't have to + * worry about the last term in the chain. */ + if (rule->vars[src_var].kind == EcsVarTable) { + flecs_rule_compile_term_id(world, rule, &op, &term->src, + &op.src, EcsRuleSrc, EcsVarEntity, ctx, true); + src_var = op.src.var; + } + } + + if (flecs_rule_compile_ensure_vars( + rule, &op, &op.src, EcsRuleSrc, ctx, cond_write, NULL)) + { + goto error; + } + + /* If source is Any (_) and first and/or second are unconstrained, insert an + * ids instruction instead of an And */ + if (term->flags & EcsTermMatchAnySrc) { + op.kind = EcsRuleIds; + /* Use up-to-date written values after potentially inserting each */ + if (!first_written || !second_written) { + if (!first_written) { + /* If first is unknown, traverse left: <- (*, t) */ + if (term->first.id != EcsAny) { + op.kind = EcsRuleIdsLeft; + } + } else { + /* If second is wildcard, traverse right: (r, *) -> */ + if (term->second.id != EcsAny) { + op.kind = EcsRuleIdsRight; + } + } + op.src.entity = 0; + op.flags &= (ecs_flags8_t)~(EcsRuleIsVar << EcsRuleSrc); /* ids has no src */ + op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleSrc); + } + + /* If source variable is not written and we're querying just for Any, insert + * a dedicated instruction that uses the Any record in the id index. Any + * queries that are evaluated against written sources can use Wildcard + * records, which is what the AndAny instruction does. */ + } else if (!src_written && term->id == EcsAny && op.kind == EcsRuleAndAny) { + /* Lookup variables ($var.child_name) are always written */ + if (!rule->vars[src_var].lookup) { + op.kind = EcsRuleSelectAny; /* Uses Any (_) id record */ + } + } + + /* If this is a transitive term and both the target and source are unknown, + * find the targets for the relationship first. This clusters together + * tables for the same target, which allows for more efficient usage of the + * traversal caches. */ + if (term->flags & EcsTermTransitive && src_is_var && second_is_var) { + if (!src_written && !second_written) { + flecs_rule_insert_unconstrained_transitive( + rule, &op, ctx, cond_write); + } + } + + /* Check if this term has variables that have been conditionally written, + * like variables written by an optional term. */ + if (ctx->cond_written) { + if (!is_or || first_or) { + flecs_rule_begin_block_cond_eval(&op, ctx, cond_write_state); + } + } + + /* Handle Not, Optional, Or operators */ + if (is_not) { + flecs_rule_begin_block(EcsRuleNot, ctx); + } else if (term->oper == EcsOptional) { + flecs_rule_begin_block(EcsRuleOptional, ctx); + } else if (first_or) { + flecs_rule_begin_block_or(&op, term, ctx); + } + + /* If term has component inheritance enabled, insert instruction to walk + * down the relationship tree of the id. */ + if (term->flags & EcsTermIdInherited) { + flecs_rule_insert_inheritance(rule, term, &op, ctx, cond_write); + } + + op.match_flags = term->flags; + + if (first_is_var) { + op.first.var = first_var; + op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); + op.flags |= (EcsRuleIsVar << EcsRuleFirst); + } + + if (term->src.flags & EcsSelf) { + op.flags |= EcsRuleIsSelf; + } + + /* Insert instructions for lookup variables */ + ecs_write_flags_t write_state = ctx->written; + if (first_is_var) { + if (flecs_rule_compile_lookup(rule, first_var, ctx, cond_write)) { + write_state |= (1ull << first_var); // lookups are resolved inline + } + } + if (src_is_var) { + if (flecs_rule_compile_lookup(rule, src_var, ctx, cond_write)) { + write_state |= (1ull << src_var); // lookups are resolved inline + } + } + if (second_is_var) { + if (flecs_rule_compile_lookup(rule, second_var, ctx, cond_write)) { + write_state |= (1ull << second_var); // lookups are resolved inline + } + } + + if (builtin_pred) { + if (flecs_rule_compile_builtin_pred(rule, term, &op, write_state)) { + goto error; + } + } + + /* If we're writing the $this variable, filter out disabled/prefab entities + * unless the filter explicitly matches them. + * This could've been done with regular With instructions, but since + * filtering out disabled/prefab entities is the default and this check is + * cheap to perform on table flags, it's worth special casing. */ + if (!src_written && src_var == 0) { + ecs_flags32_t filter_flags = filter->flags; + if (!(filter_flags & EcsFilterMatchDisabled) || + !(filter_flags & EcsFilterMatchPrefab)) + { + ecs_flags32_t table_flags = 0; + if (!(filter_flags & EcsFilterMatchDisabled)) { + table_flags |= EcsTableIsDisabled; + } + if (!(filter_flags & EcsFilterMatchPrefab)) { + table_flags |= EcsTableIsPrefab; + } + + op.other = flecs_itolbl(table_flags); + } + } + + /* After evaluating a term, a used variable is always written */ + if (src_is_var) { + flecs_rule_write(src_var, &op.written); + flecs_rule_write_ctx(op.src.var, ctx, cond_write); + } + if (first_is_var) { + flecs_rule_write(first_var, &op.written); + flecs_rule_write_ctx(first_var, ctx, cond_write); + } + if (second_is_var) { + flecs_rule_write(second_var, &op.written); + flecs_rule_write_ctx(second_var, ctx, cond_write); + } + flecs_rule_op_insert(&op, ctx); + + ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); + if (is_or) { + ecs_rule_op_t *op_ptr = ecs_vec_get_t(ctx->ops, ecs_rule_op_t, + ctx->cur->lbl_query); + op_ptr->next = FlecsRuleOrMarker; + } + + /* Handle self-references between src and first/second variables */ + if (src_is_var) { + if (first_is_var) { + flecs_rule_insert_contains(rule, src_var, first_var, ctx); + } + if (second_is_var && first_var != second_var) { + flecs_rule_insert_contains(rule, src_var, second_var, ctx); + } + } + + /* Handle self references between first and second variables */ + if (first_is_var && !first_written && (first_var == second_var)) { + flecs_rule_insert_pair_eq(term->field_index, ctx); + } + + /* Handle closing of Not, Optional and Or operators */ + if (is_not) { + flecs_rule_end_block(ctx); + } else if (term->oper == EcsOptional) { + flecs_rule_end_block(ctx); + } else if (last_or) { + flecs_rule_end_block_or(ctx); + } + + /* Handle closing of conditional evaluation */ + if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { + if (!is_or || last_or) { + flecs_rule_end_block_cond_eval(ctx); + } + } + + /* Ensure that term id is set after evaluating Not */ + if (term->flags & EcsTermIdInherited) { + if (is_not) { + ecs_rule_op_t set_id = {0}; + set_id.kind = EcsRuleSetId; + set_id.first.entity = term->id; + set_id.flags = (EcsRuleIsEntity << EcsRuleFirst); + set_id.field_index = flecs_ito(int8_t, term->field_index); + flecs_rule_op_insert(&set_id, ctx); + } + } + + return 0; +error: + return -1; +} + +static +bool flecs_rule_var_is_unknown( + ecs_rule_t *rule, + ecs_var_id_t var_id, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_var_t *vars = rule->vars; + if (ctx->written & (1ull << var_id)) { + return false; + } else { + ecs_var_id_t table_var = vars[var_id].table_id; + if (table_var != EcsVarNone) { + return flecs_rule_var_is_unknown(rule, table_var, ctx); + } + } + return true; +} + +/* Returns whether term is unkown. A term is unknown when it has variable + * elements (first, second, src) that are all unknown. */ +static +bool flecs_rule_term_is_unknown( + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t dummy = {0}; + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->first, + &dummy.first, EcsRuleFirst, EcsVarEntity, ctx, false); + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->second, + &dummy.second, EcsRuleSecond, EcsVarEntity, ctx, false); + flecs_rule_compile_term_id(NULL, rule, &dummy, &term->src, + &dummy.src, EcsRuleSrc, EcsVarAny, ctx, false); + + bool has_vars = dummy.flags & + ((EcsRuleIsVar << EcsRuleFirst) | + (EcsRuleIsVar << EcsRuleSecond) | + (EcsRuleIsVar << EcsRuleSrc)); + if (!has_vars) { + /* If term has no variables (typically terms with a static src) there + * can't be anything that's unknown. */ + return false; + } + + if (dummy.flags & (EcsRuleIsVar << EcsRuleFirst)) { + if (!flecs_rule_var_is_unknown(rule, dummy.first.var, ctx)) { + return false; + } + } + if (dummy.flags & (EcsRuleIsVar << EcsRuleSecond)) { + if (!flecs_rule_var_is_unknown(rule, dummy.second.var, ctx)) { + return false; + } + } + if (dummy.flags & (EcsRuleIsVar << EcsRuleSrc)) { + if (!flecs_rule_var_is_unknown(rule, dummy.src.var, ctx)) { + return false; + } + } + + return true; +} + +/* Find the next known term from specified offset. This function is used to find + * a term that can be evaluated before a term that is unknown. Evaluating known + * before unknown terms can significantly decrease the search space. */ +static +int32_t flecs_rule_term_next_known( + ecs_rule_t *rule, + ecs_rule_compile_ctx_t *ctx, + int32_t offset, + ecs_flags64_t compiled) +{ + ecs_filter_t *filter = &rule->filter; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + for (i = offset; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (compiled & (1ull << i)) { + continue; + } + + /* Only evaluate And terms */ + if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ + continue; + } + + /* Don't reorder terms before/after scopes */ + if (term->first.id == EcsScopeOpen || term->first.id == EcsScopeClose) { + return -1; + } + + if (flecs_rule_term_is_unknown(rule, term, ctx)) { + continue; + } + + return i; + } + + return -1; +} + +/* If the first part of a query contains more than one trivial term, insert a + * special instruction which batch-evaluates multiple terms. */ +static +int32_t flecs_rule_insert_trivial_search( + ecs_rule_t *rule, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_filter_t *filter = &rule->filter; + ecs_term_t *terms = filter->terms; + int32_t i, term_count = filter->term_count; + + /* Find trivial terms, which can be handled in single instruction */ + int32_t trivial_wildcard_terms = 0; + int32_t trivial_data_terms = 0; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (!(term->flags & EcsTermIsTrivial)) { + break; + } + + /* We can only add trivial terms to plan if they no up traversal */ + if ((term->src.flags & EcsTraverseFlags) != EcsSelf) { + break; + } + + if (ecs_id_is_wildcard(term->id)) { + trivial_wildcard_terms ++; + } + + if (!(term->flags & EcsTermNoData)) { + trivial_data_terms ++; + } + } + + int32_t trivial_terms = i; + if (trivial_terms >= 2) { + /* If there's more than 1 trivial term, batch them in trivial search */ + ecs_rule_op_t trivial = {0}; + if (trivial_wildcard_terms) { + trivial.kind = EcsRuleTrivWildcard; + } else { + if (trivial_data_terms) { + /* Check to see if there are remaining data terms. If there are, + * we'll have to insert an instruction later that populates all + * fields, so don't do double work here. */ + for (i = trivial_terms; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (!(term->flags & EcsTermIsTrivial)) { + break; + } + } + if (trivial_terms == term_count || i != term_count) { + /* Nobody else is going to set the data fields, so we should + * do it here. */ + trivial.kind = EcsRuleTrivData; + } + } + if (!trivial.kind) { + trivial.kind = EcsRuleTriv; + } + } + + /* Store on the operation how many trivial terms should be evaluated */ + trivial.other = (ecs_rule_lbl_t)trivial_terms; + flecs_rule_op_insert(&trivial, ctx); + } else { + /* If fewer than 1 trivial term, there's no point in batching them */ + trivial_terms = 0; + } + + return trivial_terms; +} + +/* Insert instruction to populate data fields. */ +static +void flecs_rule_insert_populate( + ecs_rule_t *rule, + ecs_rule_compile_ctx_t *ctx, + int32_t trivial_terms) +{ + ecs_filter_t *filter = &rule->filter; + int32_t i, term_count = filter->term_count; + + /* Insert instruction that populates data. This instruction does not + * have to be inserted if the filter provides no data, or if all terms + * of the filter are trivial, in which case the trivial search operation + * also sets the data. */ + if (!(filter->flags & EcsFilterNoData) && (trivial_terms != term_count)) { + int32_t data_fields = 0; + bool only_self = true; + + /* There are two instructions for setting data fields, a fast one + * that only supports owned fields, and one that supports any kind + * of field. Loop through (remaining) terms to check which one we + * need to use. */ + for (i = trivial_terms; i < term_count; i ++) { + ecs_term_t *term = &filter->terms[i]; + if (term->flags & EcsTermNoData) { + /* Don't care about terms that have no data */ + continue; + } + + data_fields ++; + + if (!ecs_term_match_this(term)) { + break; + } + + if (term->src.flags & EcsUp) { + break; + } + } + + if (i != filter->term_count) { + only_self = false; /* Needs the more complex operation */ + } + + if (data_fields) { + if (only_self) { + ecs_rule_op_t nothing = {0}; + nothing.kind = EcsRulePopulateSelf; + flecs_rule_op_insert(¬hing, ctx); + } else { + ecs_rule_op_t nothing = {0}; + nothing.kind = EcsRulePopulate; + flecs_rule_op_insert(¬hing, ctx); + } + } + } +} + +int flecs_rule_compile( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_rule_t *rule) +{ + ecs_filter_t *filter = &rule->filter; + ecs_term_t *terms = filter->terms; + ecs_rule_compile_ctx_t ctx = {0}; + ecs_vec_reset_t(NULL, &stage->operations, ecs_rule_op_t); + ctx.ops = &stage->operations; + ctx.cur = ctx.ctrlflow; + ctx.cur->lbl_begin = -1; + ctx.cur->lbl_begin = -1; + ecs_vec_clear(ctx.ops); + + /* Find all variables defined in query */ + if (flecs_rule_discover_vars(stage, rule)) { + return -1; + } + + /* If rule contains fixed source terms, insert operation to set sources */ + int32_t i, term_count = filter->term_count; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->src.flags & EcsIsEntity) { + ecs_rule_op_t set_fixed = {0}; + set_fixed.kind = EcsRuleSetFixed; + flecs_rule_op_insert(&set_fixed, &ctx); + break; + } + } + + /* If the rule contains terms with fixed ids (no wildcards, variables), + * insert instruction that initializes ecs_iter_t::ids. This allows for the + * insertion of simpler instructions later on. */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (flecs_rule_term_fixed_id(filter, term) || + (term->src.flags & EcsIsEntity && !term->src.id)) + { + ecs_rule_op_t set_ids = {0}; + set_ids.kind = EcsRuleSetIds; + flecs_rule_op_insert(&set_ids, &ctx); + break; + } + } + + /* Insert trivial term search if query allows for it */ + int32_t trivial_terms = flecs_rule_insert_trivial_search(rule, &ctx); + + /* Compile remaining query terms to instructions */ + ecs_flags64_t compiled = 0; + for (i = trivial_terms; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int32_t compile = i; + + if (compiled & (1ull << i)) { + continue; /* Already compiled */ + } + + bool can_reorder = true; + if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ + can_reorder = false; + } + + /* If variables have been written, but this term has no known variables, + * first try to resolve terms that have known variables. This can + * significantly reduce the search space. + * Only perform this optimization after at least one variable has been + * written to, as all terms are unknown otherwise. */ + if (can_reorder && ctx.written && + flecs_rule_term_is_unknown(rule, term, &ctx)) + { + int32_t term_index = flecs_rule_term_next_known( + rule, &ctx, i + 1, compiled); + if (term_index != -1) { + term = &rule->filter.terms[term_index]; + compile = term_index; + i --; /* Repeat current term */ + } + } + + if (flecs_rule_compile_term(world, rule, term, &ctx)) { + return -1; + } + + compiled |= (1ull << compile); + } + + ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); + + /* If This variable has been written as entity, insert an operation to + * assign it to it.entities for consistency. */ + if (this_id != EcsVarNone && (ctx.written & (1ull << this_id))) { + ecs_rule_op_t set_this = {0}; + set_this.kind = EcsRuleSetThis; + set_this.flags |= (EcsRuleIsVar << EcsRuleFirst); + set_this.first.var = this_id; + flecs_rule_op_insert(&set_this, &ctx); + } + + /* Make sure non-This variables are written as entities */ + if (rule->vars) { + for (i = 0; i < rule->var_count; i ++) { + ecs_rule_var_t *var = &rule->vars[i]; + if (var->id && var->kind == EcsVarTable && var->name) { + ecs_var_id_t var_id = flecs_rule_find_var_id(rule, var->name, + EcsVarEntity); + if (!flecs_rule_is_written(var_id, ctx.written)) { + /* Skip anonymous variables */ + if (!flecs_rule_var_is_anonymous(rule, var_id)) { + flecs_rule_insert_each(var->id, var_id, &ctx, false); + } + } + } + } + } + + /* If rule contains non-This variables as term source, build lookup array */ + if (rule->src_vars) { + ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); + bool only_anonymous = true; + + for (i = 0; i < filter->field_count; i ++) { + ecs_var_id_t var_id = rule->src_vars[i]; + if (!var_id) { + continue; + } + + if (!flecs_rule_var_is_anonymous(rule, var_id)) { + only_anonymous = false; + break; + } else { + /* Don't fetch component data for anonymous variables. Because + * not all metadata (such as it.sources) is initialized for + * anonymous variables, and because they may only be available + * as table variables (each is not guaranteed to be inserted for + * anonymous variables) the iterator may not have sufficient + * information to resolve component data. */ + for (int32_t t = 0; t < filter->term_count; t ++) { + ecs_term_t *term = &filter->terms[t]; + if (term->field_index == i) { + term->inout = EcsInOutNone; + } + } + } + } + + /* Don't insert setvar instruction if all vars are anonymous */ + if (!only_anonymous) { + ecs_rule_op_t set_vars = {0}; + set_vars.kind = EcsRuleSetVars; + flecs_rule_op_insert(&set_vars, &ctx); + } + + for (i = 0; i < filter->field_count; i ++) { + ecs_var_id_t var_id = rule->src_vars[i]; + if (!var_id) { + continue; + } + + if (rule->vars[var_id].kind == EcsVarTable) { + var_id = flecs_rule_find_var_id(rule, rule->vars[var_id].name, + EcsVarEntity); + + /* Variables used as source that aren't This must be entities */ + ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } + + rule->src_vars[i] = var_id; + } + } + + /* If filter is empty, insert Nothing instruction */ + if (!term_count) { + ecs_rule_op_t nothing = {0}; + nothing.kind = EcsRuleNothing; + flecs_rule_op_insert(¬hing, &ctx); + } else { + /* Insert instruction to populate data fields */ + flecs_rule_insert_populate(rule, &ctx, trivial_terms); + + /* Insert yield. If program reaches this operation, a result was found */ + ecs_rule_op_t yield = {0}; + yield.kind = EcsRuleYield; + flecs_rule_op_insert(&yield, &ctx); + } + + int32_t op_count = ecs_vec_count(ctx.ops); + if (op_count) { + rule->op_count = op_count; + rule->ops = ecs_os_malloc_n(ecs_rule_op_t, op_count); + ecs_rule_op_t *rule_ops = ecs_vec_first_t(ctx.ops, ecs_rule_op_t); + ecs_os_memcpy_n(rule->ops, rule_ops, ecs_rule_op_t, op_count); + } + + return 0; +} + +#endif + +/** + * @file addons/rules/engine.c + * @brief Rules engine implementation. + */ + + +#ifdef FLECS_RULES + +static +bool flecs_rule_dispatch( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx); + +static +bool flecs_rule_run_until( + bool redo, + ecs_rule_run_ctx_t *ctx, + const ecs_rule_op_t *ops, + ecs_rule_lbl_t first, + ecs_rule_lbl_t cur, + ecs_rule_op_kind_t until); + +ecs_allocator_t* flecs_rule_get_allocator( + const ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + if (ecs_poly_is(world, ecs_world_t)) { + return &world->allocator; + } else { + ecs_assert(ecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); + return &((ecs_stage_t*)world)->allocator; + } +} + +static +ecs_rule_op_ctx_t* flecs_op_ctx_( + const ecs_rule_run_ctx_t *ctx) +{ + return &ctx->op_ctx[ctx->op_index]; +} + +#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) + +static +void flecs_reset_source_set_flag( + const ecs_rule_run_ctx_t *ctx, + int32_t field_index) +{ + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + (*ctx->source_set) &= ~(1u << field_index); +} + +static +void flecs_set_source_set_flag( + const ecs_rule_run_ctx_t *ctx, + int32_t field_index) +{ + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + (*ctx->source_set) |= (1u << field_index); +} + +static +ecs_table_range_t flecs_range_from_entity( + ecs_entity_t e, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_record_t *r = flecs_entities_get(ctx->world, e); + if (!r) { + return (ecs_table_range_t){ 0 }; + } + return (ecs_table_range_t){ + .table = r->table, + .offset = ECS_RECORD_TO_ROW(r->row), + .count = 1 + }; +} + +static +ecs_table_range_t flecs_rule_var_get_range( + int32_t var_id, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_assert(var_id < ctx->rule->var_count, ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return var->range; + } + + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(entity, ctx); + return var->range; + } + + return (ecs_table_range_t){ 0 }; +} + +static +ecs_table_t* flecs_rule_var_get_table( + int32_t var_id, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return table; + } + + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(entity, ctx); + return var->range.table; + } + + return NULL; +} + +static +ecs_table_t* flecs_rule_get_table( + const ecs_rule_op_t *op, + const ecs_rule_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); + if (flags & EcsRuleIsEntity) { + return ecs_get_table(ctx->world, ref->entity); + } else { + return flecs_rule_var_get_table(ref->var, ctx); + } +} + +static +ecs_table_range_t flecs_rule_get_range( + const ecs_rule_op_t *op, + const ecs_rule_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); + if (flags & EcsRuleIsEntity) { + ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); + return flecs_range_from_entity(ref->entity, ctx); + } else { + ecs_var_t *var = &ctx->vars[ref->var]; + if (var->range.table) { + return ctx->vars[ref->var].range; + } else if (var->entity) { + return flecs_range_from_entity(var->entity, ctx); + } + } + return (ecs_table_range_t){0}; +} + +static +ecs_entity_t flecs_rule_var_get_entity( + ecs_var_id_t var_id, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_assert(var_id < (ecs_var_id_t)ctx->rule->var_count, + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + ecs_entity_t entity = var->entity; + if (entity) { + return entity; + } + + ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = var->range.table; + ecs_entity_t *entities = table->data.entities.array; + var->entity = entities[var->range.offset]; + return var->entity; +} + +static +void flecs_rule_var_reset( + ecs_var_id_t var_id, + const ecs_rule_run_ctx_t *ctx) +{ + ctx->vars[var_id].entity = EcsWildcard; + ctx->vars[var_id].range.table = NULL; +} + +static +void flecs_rule_var_set_table( + const ecs_rule_op_t *op, + ecs_var_id_t var_id, + ecs_table_t *table, + int32_t offset, + int32_t count, + const ecs_rule_run_ctx_t *ctx) +{ + (void)op; + ecs_assert(ctx->rule_vars[var_id].kind == EcsVarTable, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_rule_is_written(var_id, op->written), + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->entity = 0; + var->range = (ecs_table_range_t){ + .table = table, + .offset = offset, + .count = count + }; +} + +static +void flecs_rule_var_set_entity( + const ecs_rule_op_t *op, + ecs_var_id_t var_id, + ecs_entity_t entity, + const ecs_rule_run_ctx_t *ctx) +{ + (void)op; + ecs_assert(var_id < (ecs_var_id_t)ctx->rule->var_count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_rule_is_written(var_id, op->written), + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->range.table = NULL; + var->entity = entity; +} + +static +void flecs_rule_set_vars( + const ecs_rule_op_t *op, + ecs_id_t id, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + + if (flags_1st & EcsRuleIsVar) { + ecs_var_id_t var = op->first.var; + if (op->written & (1ull << var)) { + if (ECS_IS_PAIR(id)) { + flecs_rule_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); + } else { + flecs_rule_var_set_entity(op, var, id, ctx); + } + } + } + if (flags_2nd & EcsRuleIsVar) { + ecs_var_id_t var = op->second.var; + if (op->written & (1ull << var)) { + flecs_rule_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); + } + } +} + +static +ecs_table_range_t flecs_get_ref_range( + const ecs_rule_ref_t *ref, + ecs_flags16_t flag, + const ecs_rule_run_ctx_t *ctx) +{ + if (flag & EcsRuleIsEntity) { + return flecs_range_from_entity(ref->entity, ctx); + } else if (flag & EcsRuleIsVar) { + return flecs_rule_var_get_range(ref->var, ctx); + } + return (ecs_table_range_t){0}; +} + +static +ecs_entity_t flecs_get_ref_entity( + const ecs_rule_ref_t *ref, + ecs_flags16_t flag, + const ecs_rule_run_ctx_t *ctx) +{ + if (flag & EcsRuleIsEntity) { + return ref->entity; + } else if (flag & EcsRuleIsVar) { + return flecs_rule_var_get_entity(ref->var, ctx); + } + return 0; +} + +static +ecs_id_t flecs_rule_op_get_id_w_written( + const ecs_rule_op_t *op, + uint64_t written, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + ecs_entity_t first = 0, second = 0; + + if (flags_1st) { + if (flecs_ref_is_written(op, &op->first, EcsRuleFirst, written)) { + first = flecs_get_ref_entity(&op->first, flags_1st, ctx); + } else if (flags_1st & EcsRuleIsVar) { + first = EcsWildcard; + } + } + if (flags_2nd) { + if (flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { + second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); + } else if (flags_2nd & EcsRuleIsVar) { + second = EcsWildcard; + } + } + + if (flags_2nd & (EcsRuleIsVar | EcsRuleIsEntity)) { + return ecs_pair(first, second); + } else { + return flecs_entities_get_generation(ctx->world, first); + } +} + +static +ecs_id_t flecs_rule_op_get_id( + const ecs_rule_op_t *op, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + return flecs_rule_op_get_id_w_written(op, written, ctx); +} + +static +int16_t flecs_rule_next_column( + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { + column = column + 1; + } else { + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + column = ecs_search_offset(NULL, table, column + 1, id, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + } + return flecs_ito(int16_t, column); +} + +static +void flecs_rule_it_set_column( + ecs_iter_t *it, + int32_t field_index, + int32_t column) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + it->columns[field_index] = column + 1; +} + +static +ecs_id_t flecs_rule_it_set_id( + ecs_iter_t *it, + ecs_table_t *table, + int32_t field_index, + int32_t column) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + return it->ids[field_index] = table->type.array[column]; +} + +static +void flecs_rule_set_match( + const ecs_rule_op_t *op, + ecs_table_t *table, + int32_t column, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t field_index = op->field_index; + if (field_index == -1) { + return; + } + + ecs_iter_t *it = ctx->it; + flecs_rule_it_set_column(it, field_index, column); + ecs_id_t matched = flecs_rule_it_set_id(it, table, field_index, column); + flecs_rule_set_vars(op, matched, ctx); +} + +static +void flecs_rule_set_trav_match( + const ecs_rule_op_t *op, + int32_t column, + ecs_entity_t trav, + ecs_entity_t second, + const ecs_rule_run_ctx_t *ctx) +{ + int32_t field_index = op->field_index; + if (field_index == -1) { + return; + } + + ecs_iter_t *it = ctx->it; + ecs_id_t matched = ecs_pair(trav, second); + it->ids[op->field_index] = matched; + if (column != -1) { + flecs_rule_it_set_column(it, op->field_index, column); + } + flecs_rule_set_vars(op, matched, ctx); +} + +static +bool flecs_rule_table_filter( + ecs_table_t *table, + ecs_rule_lbl_t other, + ecs_flags32_t filter_mask) +{ + uint32_t filter = flecs_ito(uint32_t, other); + return (table->flags & filter_mask & filter) != 0; +} + +static +bool flecs_rule_select_w_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx, + ecs_id_t id, + ecs_flags32_t filter_mask) +{ + ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_id_record_t *idr = op_ctx->idr; + ecs_table_record_t *tr; + ecs_table_t *table; + + if (!redo) { + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } + } + + if (ctx->rule->filter.flags & EcsFilterMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } else { + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } + } + +repeat: + if (!redo || !op_ctx->remaining) { + tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } + + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); + table = tr->hdr.table; + flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); + } else { + tr = (ecs_table_record_t*)op_ctx->it.cur; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + table = tr->hdr.table; + op_ctx->column = flecs_rule_next_column(table, idr->id, op_ctx->column); + op_ctx->remaining --; + } + + if (flecs_rule_table_filter(table, op->other, filter_mask)) { + goto repeat; + } + + flecs_rule_set_match(op, table, op_ctx->column, ctx); + return true; +} + +static +bool flecs_rule_select( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_id_t id = 0; + if (!redo) { + id = flecs_rule_op_get_id(op, ctx); + } + return flecs_rule_select_w_id(op, redo, ctx, id, + (EcsTableIsPrefab|EcsTableIsDisabled)); +} + +static +bool flecs_rule_with( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_id_record_t *idr = op_ctx->idr; + const ecs_table_record_t *tr; + + ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); + if (!table) { + return false; + } + + if (!redo) { + ecs_id_t id = flecs_rule_op_get_id(op, ctx); + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } + } + + tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } + + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count); + } else { + if (--op_ctx->remaining <= 0) { + return false; + } + + op_ctx->column = flecs_rule_next_column(table, idr->id, op_ctx->column); + ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); + } + + flecs_rule_set_match(op, table, op_ctx->column, ctx); + return true; +} + +static +bool flecs_rule_and( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_rule_with(op, redo, ctx); + } else { + return flecs_rule_select(op, redo, ctx); + } +} + +static +bool flecs_rule_select_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx, + ecs_flags32_t table_filter) +{ + ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); + + if (!redo) { + ecs_id_t id = it->ids[field]; + ecs_id_record_t *idr = op_ctx->idr; + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } + } + + if (ctx->rule->filter.flags & EcsFilterMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } else { + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } + } + +repeat: {} + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } + + ecs_table_t *table = tr->hdr.table; + if (flecs_rule_table_filter(table, op->other, table_filter)) { + goto repeat; + } + + flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); + flecs_rule_it_set_column(it, field, tr->index); + return true; +} + +static +bool flecs_rule_with_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + + ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); + if (!table) { + return false; + } + + ecs_id_t id = it->ids[field]; + ecs_id_record_t *idr = op_ctx->idr; + if (!idr || idr->id != id) { + idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); + if (!idr) { + return false; + } + } + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } + + flecs_rule_it_set_column(it, field, tr->index); + return true; +} + +static +bool flecs_rule_and_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_rule_with_id(op, redo, ctx); + } else { + return flecs_rule_select_id(op, redo, ctx, + (EcsTableIsPrefab|EcsTableIsDisabled)); + } +} + +static +bool flecs_rule_up_select( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx, + bool self, + bool id_only) +{ + ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_world_t *world = ctx->world; + ecs_iter_t *it = ctx->it; + bool redo_select = redo; + const ecs_filter_t *filter = &ctx->rule->filter; + + /* Early out if traversal relationship doesn't exist */ + op_ctx->trav = filter->terms[op->term_index].src.trav; + if (!op_ctx->idr_trav) { + op_ctx->idr_trav = flecs_id_record_get(ctx->world, + ecs_pair(op_ctx->trav, EcsWildcard)); + } + if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)){ + if (!self) { + return false; + } else if (id_only) { + return flecs_rule_select_id(op, redo, ctx, + (EcsTableIsPrefab|EcsTableIsDisabled)); + } else { + return flecs_rule_select(op, redo, ctx); + } + } + + if (!redo) { + op_ctx->with = flecs_rule_op_get_id(op, ctx); + op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); + if (!op_ctx->idr_with) { + return false; + } + + op_ctx->down = NULL; + op_ctx->cache_elem = 0; + } + + ecs_trav_down_t *down = op_ctx->down; + + do { + while (!down) { + ecs_table_t *table = op_ctx->table; + if (!table) { + ecs_table_range_t range; + it->sources[op->field_index] = 0; + do { + bool result; + if (id_only) { + result = flecs_rule_select_id(op, redo_select, ctx, 0); + } else { + result = flecs_rule_select_w_id(op, redo_select, ctx, + op_ctx->with, 0); + } + if (!result) { + return false; + } + + redo_select = true; + + range = flecs_rule_get_range( + op, &op->src, EcsRuleSrc, ctx); + ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + } while (!self && range.table->_->traversable_count == 0); + + if (!range.count) { + range.count = ecs_table_count(range.table); + } + + table = op_ctx->table = range.table; + op_ctx->row = range.offset; + op_ctx->end = range.offset + range.count; + op_ctx->matched = it->ids[op->field_index]; + + if (self) { + if (!flecs_rule_table_filter(table, op->other, + (EcsTableIsPrefab|EcsTableIsDisabled))) + { + flecs_reset_source_set_flag(ctx, op->field_index); + op_ctx->row --; + return true; + } + } + + redo_select = true; + } else { + op_ctx->row ++; + } + + if (table->_->traversable_count == 0) { + op_ctx->table = NULL; + continue; + } else { + int32_t row; + ecs_entity_t entity = 0; + ecs_entity_t *entities = flecs_table_entities_array(table); + + for (row = op_ctx->row; row < op_ctx->end; row ++) { + entity = entities[row]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (record->row & EcsEntityIsTraversable) { + it->sources[op->field_index] = entity; + break; + } + } + + if (row == op_ctx->end) { + op_ctx->table = NULL; + continue; + } + + down = op_ctx->down = flecs_rule_get_down_cache(ctx, &op_ctx->cache, + op_ctx->trav, entity, op_ctx->idr_with, self); + op_ctx->cache_elem = -1; + } + } + +next_elem: + if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { + down = NULL; + continue; + } + + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); + + flecs_rule_var_set_table(op, op->src.var, elem->table, 0, 0, ctx); + flecs_rule_set_vars(op, op_ctx->matched, ctx); + + if (flecs_rule_table_filter(elem->table, op->other, + (EcsTableIsPrefab|EcsTableIsDisabled))) + { + goto next_elem; + } + + break; + } while (true); + + flecs_set_source_set_flag(ctx, op->field_index); + + return true; +} + +static +bool flecs_rule_up_with( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + const ecs_filter_t *filter = &ctx->rule->filter; + ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_iter_t *it = ctx->it; + + /* Early out if traversal relationship doesn't exist */ + op_ctx->trav = filter->terms[op->term_index].src.trav; + if (!op_ctx->idr_trav) { + op_ctx->idr_trav = flecs_id_record_get(ctx->world, + ecs_pair(op_ctx->trav, EcsWildcard)); + } + if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)){ + return false; + } + + if (!redo) { + op_ctx->trav = filter->terms[op->term_index].src.trav; + op_ctx->with = flecs_rule_op_get_id(op, ctx); + op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); + + if (!op_ctx->idr_with) { + return false; + } + + ecs_table_range_t range = flecs_rule_get_range( + op, &op->src, EcsRuleSrc, ctx); + if (!range.table) { + return false; + } + + ecs_trav_up_t *up = flecs_rule_get_up_cache(ctx, &op_ctx->cache, + range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, + op_ctx->idr_trav); + + if (!up) { + return false; + } + + it->sources[op->field_index] = flecs_entities_get_generation( + ctx->world, up->src); + it->columns[op->field_index] = up->column + 1; + it->ids[op->field_index] = up->id; + flecs_rule_set_vars(op, up->id, ctx); + flecs_set_source_set_flag(ctx, op->field_index); + return true; + } else { + return false; + } +} + +static +bool flecs_rule_self_up_with( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx, + bool id_only) +{ + if (!redo) { + bool result; + if (id_only) { + result = flecs_rule_with_id(op, redo, ctx); + } else { + result = flecs_rule_with(op, redo, ctx); + } + if (result) { + ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + op_ctx->trav = 0; + if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar) { + ecs_iter_t *it = ctx->it; + it->sources[op->field_index] = 0; + } + return true; + } + + flecs_reset_source_set_flag(ctx, op->field_index); + + return flecs_rule_up_with(op, redo, ctx); + } else { + ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + if (op_ctx->trav == 0) { + return flecs_rule_with(op, redo, ctx); + } + } + + return false; +} + +static +bool flecs_rule_up( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { + return flecs_rule_up_with(op, redo, ctx); + } else { + return flecs_rule_up_select(op, redo, ctx, false, false); + } +} + +static +bool flecs_rule_self_up( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { + return flecs_rule_self_up_with(op, redo, ctx, false); + } else { + return flecs_rule_up_select(op, redo, ctx, true, false); + } +} + +static +bool flecs_rule_up_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { + return flecs_rule_up_with(op, redo, ctx); + } else { + return flecs_rule_up_select(op, redo, ctx, false, true); + } +} + +static +bool flecs_rule_self_up_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { + return flecs_rule_self_up_with(op, redo, ctx, true); + } else { + return flecs_rule_up_select(op, redo, ctx, true, true); + } +} + +static +bool flecs_rule_select_any( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + return flecs_rule_select_w_id(op, redo, ctx, EcsAny, + (EcsTableIsPrefab|EcsTableIsDisabled)); +} + +static +bool flecs_rule_and_any( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t match_flags = op->match_flags; + if (redo) { + if (match_flags & EcsTermMatchAnySrc) { + return false; + } + } + + uint64_t written = ctx->written[ctx->op_index]; + int32_t remaining = 1; + bool result; + if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { + result = flecs_rule_with(op, redo, ctx); + } else { + result = flecs_rule_select(op, redo, ctx); + remaining = 0; + } + + if (!redo) { + ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + if (match_flags & EcsTermMatchAny && op_ctx->remaining) { + op_ctx->remaining = flecs_ito(int16_t, remaining); + } + } + + int32_t field = op->field_index; + if (field != -1) { + ctx->it->ids[field] = flecs_rule_op_get_id(op, ctx); + } + + return result; +} + +static +bool flecs_rule_triv( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); + int32_t until = flecs_ito(int32_t, op->other); + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_rule_trivial_test(ctx->rule, ctx, !redo, until); + } else { + return flecs_rule_trivial_search_nodata(ctx->rule, ctx, op_ctx, !redo, until); + } +} + +static +bool flecs_rule_triv_data( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); + int32_t until = flecs_ito(int32_t, op->other); + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_rule_trivial_test(ctx->rule, ctx, !redo, until); + } else { + return flecs_rule_trivial_search(ctx->rule, ctx, op_ctx, !redo, until); + } +} + +static +bool flecs_rule_triv_wildcard( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); + int32_t until = flecs_ito(int32_t, op->other); + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_rule_trivial_test_w_wildcards(ctx->rule, ctx, !redo, until); + } else { + return flecs_rule_trivial_search_w_wildcards(ctx->rule, ctx, op_ctx, !redo, until); + } +} + +static +bool flecs_rule_trav_fixed_src_reflexive( + const ecs_rule_op_t *op, + const ecs_rule_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav, + ecs_entity_t second) +{ + ecs_table_t *table = range->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *entities = table->data.entities.array; + int32_t count = range->count; + if (!count) { + count = ecs_table_count(table); + } + + int32_t i = range->offset, end = i + count; + for (; i < end; i ++) { + if (entities[i] == second) { + /* Even though table doesn't have the specific relationship + * pair, the relationship is reflexive and the target entity + * is stored in the table. */ + break; + } + } + if (i == end) { + /* Table didn't contain target entity */ + return false; + } + if (count > 1) { + /* If the range contains more than one entity, set the range to + * return only the entity matched by the reflexive property. */ + ecs_assert(flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[op->src.var]; + ecs_table_range_t *var_range = &var->range; + var_range->offset = i; + var_range->count = 1; + var->entity = entities[i]; + } + + flecs_rule_set_trav_match(op, -1, trav, second, ctx); + return true; +} + +static +bool flecs_rule_trav_unknown_src_reflexive( + const ecs_rule_op_t *op, + const ecs_rule_run_ctx_t *ctx, + ecs_entity_t trav, + ecs_entity_t second) +{ + ecs_assert(flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_id_t src_var = op->src.var; + flecs_rule_var_set_entity(op, src_var, second, ctx); + flecs_rule_var_get_table(src_var, ctx); + flecs_rule_set_trav_match(op, -1, trav, second, ctx); + return true; +} + +static +bool flecs_rule_trav_fixed_src_up_fixed_second( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; /* If everything's fixed, can only have a single result */ + } + + ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t f_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + ecs_flags16_t f_src = flecs_rule_ref_flags(op->flags, EcsRuleSrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; + + /* Check if table has transitive relationship by traversing upwards */ + int32_t column = ecs_search_relation(ctx->world, table, 0, + ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, NULL); + if (column == -1) { + if (op->match_flags & EcsTermReflexive) { + return flecs_rule_trav_fixed_src_reflexive(op, ctx, + &range, trav, second); + } else { + return false; + } + } + + flecs_rule_set_trav_match(op, column, trav, second, ctx); + return true; +} + +static +bool flecs_rule_trav_unknown_src_up_fixed_second( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t f_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + + if (!redo) { + ecs_record_t *r_second = flecs_entities_get(ctx->world, second); + bool traversable = r_second && r_second->row & EcsEntityIsTraversable; + bool reflexive = op->match_flags & EcsTermReflexive; + if (!traversable && !reflexive) { + trav_ctx->cache.id = 0; + + /* If there's no record for the entity, it can't have a subtree so + * forward operation to a regular select. */ + return flecs_rule_select(op, redo, ctx); + } + + /* Entity is traversable, which means it could have a subtree */ + flecs_rule_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); + trav_ctx->index = 0; + + if (op->match_flags & EcsTermReflexive) { + trav_ctx->index = -1; + return flecs_rule_trav_unknown_src_reflexive( + op, ctx, trav, second); + } + } else { + if (!trav_ctx->cache.id) { + /* No traversal cache, which means this is a regular select */ + return flecs_rule_select(op, redo, ctx); + } + } + + if (trav_ctx->index == -1) { + redo = false; /* First result after handling reflexive relationship */ + trav_ctx->index = 0; + } + + /* Forward to select */ + int32_t count = ecs_vec_count(&trav_ctx->cache.entities); + ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); + for (; trav_ctx->index < count; trav_ctx->index ++) { + ecs_trav_elem_t *el = &elems[trav_ctx->index]; + trav_ctx->and.idr = el->idr; /* prevents lookup by select */ + if (flecs_rule_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), + (EcsTableIsPrefab|EcsTableIsDisabled))) + { + return true; + } + + redo = false; + } + + return false; +} + +static +bool flecs_rule_trav_yield_reflexive_src( + const ecs_rule_op_t *op, + const ecs_rule_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav) +{ + ecs_var_t *vars = ctx->vars; + ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + int32_t offset = trav_ctx->offset, count = trav_ctx->count; + bool src_is_var = op->flags & (EcsRuleIsVar << EcsRuleSrc); + + if (trav_ctx->index >= (offset + count)) { + /* Restore previous offset, count */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + vars[src_var].range.offset = offset; + vars[src_var].range.count = count; + vars[src_var].entity = 0; + } + return false; + } + + ecs_entity_t entity = ecs_vec_get_t( + &range->table->data.entities, ecs_entity_t, trav_ctx->index)[0]; + flecs_rule_set_trav_match(op, -1, trav, entity, ctx); + + /* Hijack existing variable to return one result at a time */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + ecs_table_t *table = vars[src_var].range.table; + ecs_assert(!table || table == ecs_get_table(ctx->world, entity), + ECS_INTERNAL_ERROR, NULL); + (void)table; + vars[src_var].entity = entity; + vars[src_var].range = flecs_range_from_entity(entity, ctx); + } + + return true; +} + +static +bool flecs_rule_trav_fixed_src_up_unknown_second( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t f_src = flecs_rule_ref_flags(op->flags, EcsRuleSrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; + ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + + if (!redo) { + flecs_rule_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); + trav_ctx->index = 0; + if (op->match_flags & EcsTermReflexive) { + trav_ctx->yield_reflexive = true; + trav_ctx->index = range.offset; + trav_ctx->offset = range.offset; + trav_ctx->count = range.count ? range.count : ecs_table_count(table); + } + } else { + trav_ctx->index ++; + } + + if (trav_ctx->yield_reflexive) { + if (flecs_rule_trav_yield_reflexive_src(op, ctx, &range, trav)) { + return true; + } + trav_ctx->yield_reflexive = false; + trav_ctx->index = 0; + } + + if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { + return false; + } + + ecs_trav_elem_t *el = ecs_vec_get_t( + &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); + flecs_rule_set_trav_match(op, el->column, trav, el->entity, ctx); + return true; +} + +static +bool flecs_rule_trav( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + + if (!flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { + if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { + /* This can't happen, src or second should have been resolved */ + ecs_abort(ECS_INTERNAL_ERROR, + "invalid instruction sequence: unconstrained traversal"); + } else { + return flecs_rule_trav_unknown_src_up_fixed_second(op, redo, ctx); + } + } else { + if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { + return flecs_rule_trav_fixed_src_up_unknown_second(op, redo, ctx); + } else { + return flecs_rule_trav_fixed_src_up_fixed_second(op, redo, ctx); + } + } +} + +static +bool flecs_rule_ids( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + + ecs_id_record_t *cur; + ecs_id_t id = flecs_rule_op_get_id(op, ctx); + + { + cur = flecs_id_record_get(ctx->world, id); + if (!cur || !cur->cache.tables.count) { + return false; + } + } + + flecs_rule_set_vars(op, cur->id, ctx); + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + it->columns[op->field_index] = -1; /* Mark field as set */ + } + + return true; +} + +static +bool flecs_rule_idsright( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_id_record_t *cur; + + if (!redo) { + ecs_id_t id = flecs_rule_op_get_id(op, ctx); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_rule_set_vars(op, id, ctx); + return true; + } + + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; + } + } else { + if (!op_ctx->cur) { + return false; + } + } + + do { + cur = op_ctx->cur = op_ctx->cur->first.next; + } while (cur && !cur->cache.tables.count); /* Skip empty ids */ + + if (!cur) { + return false; + } + + flecs_rule_set_vars(op, cur->id, ctx); + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_rule_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + it->columns[op->field_index] = -1; /* Mark field as set */ + } + + return true; +} + +static +bool flecs_rule_idsleft( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_id_record_t *cur; + + if (!redo) { + ecs_id_t id = flecs_rule_op_get_id(op, ctx); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_rule_set_vars(op, id, ctx); + return true; + } + + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; + } + } else { + if (!op_ctx->cur) { + return false; + } + } + + do { + cur = op_ctx->cur = op_ctx->cur->second.next; + } while (cur && !cur->cache.tables.count); /* Skip empty ids */ + + if (!cur) { + return false; + } + + flecs_rule_set_vars(op, cur->id, ctx); + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_rule_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + it->columns[op->field_index] = -1; /* Mark field as set */ + } + + return true; +} + +static +bool flecs_rule_each( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); + int32_t row; + + ecs_table_range_t range = flecs_rule_var_get_range(op->first.var, ctx); + ecs_table_t *table = range.table; + if (!table) { + return false; + } + + if (!redo) { + row = op_ctx->row = range.offset; + } else { + int32_t end = range.count; + if (end) { + end += range.offset; + } else { + end = table->data.entities.count; + } + row = ++ op_ctx->row; + if (op_ctx->row >= end) { + return false; + } + } + + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entities = table->data.entities.array; + ecs_entity_t e; + do { + if (row == ecs_table_count(table)) { + return false; + } + e = entities[row ++]; + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + + /* Exclude entities that are used as markers by rule engine */ + } while ((e == EcsWildcard) || (e == EcsAny) || + (e == EcsThis) || (e == EcsVariable)); + + flecs_rule_var_set_entity(op, op->src.var, e, ctx); + + return true; +} + +static +bool flecs_rule_store( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + if (!redo) { + flecs_rule_var_set_entity(op, op->src.var, op->first.entity, ctx); + return true; + } else { + return false; + } +} + +static +bool flecs_rule_reset( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) +{ + if (!redo) { + return true; + } else { + flecs_rule_var_reset(op->src.var, ctx); + return false; + } +} + +static +void flecs_rule_reset_after_block( + const ecs_rule_op_t *start_op, + ecs_rule_run_ctx_t *ctx, + ecs_rule_ctrl_ctx_t *op_ctx) +{ + ecs_rule_lbl_t op_index = start_op->next; + const ecs_rule_op_t *op = &ctx->rit->ops[op_index]; + ctx->written[op_index] = ctx->written[ctx->op_index]; + ctx->op_index = op_index; + + int32_t field = op->field_index; + if (field == -1) { + goto done; + } + + ecs_iter_t *it = ctx->it; + + /* Not terms return no data */ + it->columns[field] = 0; + + /* Ignore variables written by Not operation */ + uint64_t *written = ctx->written; + uint64_t written_cur = written[op->prev + 1]; + ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); + ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + + /* Overwrite id with cleared out variables */ + ecs_id_t id = flecs_rule_op_get_id(op, ctx); + if (id) { + it->ids[field] = id; + } + + /* Reset variables */ + if (flags_1st & EcsRuleIsVar) { + if (!flecs_ref_is_written(op, &op->first, EcsRuleFirst, written_cur)){ + flecs_rule_var_reset(op->first.var, ctx); + } + } + if (flags_2nd & EcsRuleIsVar) { + if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ + flecs_rule_var_reset(op->second.var, ctx); + } + } + + /* If term has entity src, set it because no other instruction might */ + if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { + it->sources[field] = op->src.entity; + } + +done: + op_ctx->op_index = op_index; +} + +static +const char* flecs_rule_name_arg( + const ecs_rule_op_t *op, + ecs_rule_run_ctx_t *ctx) +{ + int8_t term_index = op->term_index; + ecs_term_t *term = &ctx->rule->filter.terms[term_index]; + return term->second.name; +} + +static +bool flecs_rule_compare_range( + const ecs_table_range_t *l, + const ecs_table_range_t *r) +{ + if (l->table != r->table) { + return false; + } + + if (l->count) { + int32_t l_end = l->offset + l->count; + int32_t r_end = r->offset + r->count; + if (r->offset < l->offset) { + return false; + } + if (r_end > l_end) { + return false; + } + } else { + /* Entire table is matched */ + } + + return true; +} + +static +bool flecs_rule_pred_eq_w_range( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + ecs_table_range_t r) +{ + if (redo) { + return false; + } + + uint64_t written = ctx->written[ctx->op_index]; + ecs_var_id_t first_var = op->first.var; + if (!(written & (1ull << first_var))) { + /* left = unknown, right = known. Assign right-hand value to left */ + ecs_var_id_t l = first_var; + ctx->vars[l].range = r; + if (r.count == 1) { + ctx->vars[l].entity = ecs_vec_get_t(&r.table->data.entities, + ecs_entity_t, r.offset)[0]; + } + return true; + } else { + ecs_table_range_t l = flecs_rule_get_range( + op, &op->first, EcsRuleFirst, ctx); + + if (!flecs_rule_compare_range(&l, &r)) { + return false; + } + + ctx->vars[first_var].range.offset = r.offset; + ctx->vars[first_var].range.count = r.count; + return true; + } +} + +static +bool flecs_rule_pred_eq( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized eq operand"); + + ecs_table_range_t r = flecs_rule_get_range( + op, &op->second, EcsRuleSecond, ctx); + return flecs_rule_pred_eq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_eq_name( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + const char *name = flecs_rule_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return false; + } + + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_eq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_neq_w_range( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + ecs_table_range_t r) +{ + ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + ecs_var_id_t first_var = op->first.var; + ecs_table_range_t l = flecs_rule_get_range( + op, &op->first, EcsRuleFirst, ctx); + + /* If tables don't match, neq always returns once */ + if (l.table != r.table) { + return true && !redo; + } + + int32_t l_offset; + int32_t l_count; + if (!redo) { + /* Make sure we're working with the correct table count */ + if (!l.count && l.table) { + l.count = ecs_table_count(l.table); + } + + l_offset = l.offset; + l_count = l.count; + + /* Cache old value */ + op_ctx->range = l; + } else { + l_offset = op_ctx->range.offset; + l_count = op_ctx->range.count; + } + + /* If the table matches, a Neq returns twice: once for the slice before the + * excluded slice, once for the slice after the excluded slice. If the right + * hand range starts & overlaps with the left hand range, there is only + * one slice. */ + ecs_var_t *var = &ctx->vars[first_var]; + if (!redo && r.offset > l_offset) { + int32_t end = r.offset; + if (end > l_count) { + end = l_count; + } + + /* Return first slice */ + var->range.table = l.table; + var->range.offset = l_offset; + var->range.count = end - l_offset; + op_ctx->redo = false; + return true; + } else if (!op_ctx->redo) { + int32_t l_end = op_ctx->range.offset + l_count; + int32_t r_end = r.offset + r.count; + + if (l_end <= r_end) { + /* If end of existing range falls inside the excluded range, there's + * nothing more to return */ + var->range = l; + return false; + } + + /* Return second slice */ + var->range.table = l.table; + var->range.offset = r_end; + var->range.count = l_end - r_end; + + /* Flag so we know we're done the next redo */ + op_ctx->redo = true; + return true; + } else { + /* Restore previous value */ + var->range = l; + return false; + } +} + +static +bool flecs_rule_pred_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + bool is_neq) +{ + ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(flecs_ref_is_written(op, &op->first, EcsRuleFirst, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized match operand"); + (void)written; + + ecs_var_id_t first_var = op->first.var; + const char *match = flecs_rule_name_arg(op, ctx); + ecs_table_range_t l; + if (!redo) { + l = flecs_rule_get_range(op, &op->first, EcsRuleFirst, ctx); + if (!l.table) { + return false; + } + + if (!l.count) { + l.count = ecs_table_count(l.table); + } + + op_ctx->range = l; + op_ctx->index = l.offset; + op_ctx->name_col = flecs_ito(int16_t, + ecs_table_get_type_index(ctx->world, l.table, + ecs_pair(ecs_id(EcsIdentifier), EcsName))); + if (op_ctx->name_col == -1) { + return is_neq; + } + op_ctx->name_col = flecs_ito(int16_t, + l.table->column_map[op_ctx->name_col]); + ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); + } else { + if (op_ctx->name_col == -1) { + /* Table has no name */ + return false; + } + + l = op_ctx->range; + } + + const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data.array; + int32_t count = l.offset + l.count, offset = -1; + for (; op_ctx->index < count; op_ctx->index ++) { + const char *name = names[op_ctx->index].value; + bool result = strstr(name, match); + if (is_neq) { + result = !result; + } + + if (!result) { + if (offset != -1) { + break; + } + } else { + if (offset == -1) { + offset = op_ctx->index; + } + } + } + + if (offset == -1) { + ctx->vars[first_var].range = op_ctx->range; + return false; + } + + ctx->vars[first_var].range.offset = offset; + ctx->vars[first_var].range.count = (op_ctx->index - offset); + return true; +} + +static +bool flecs_rule_pred_eq_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + return flecs_rule_pred_match(op, redo, ctx, false); +} + +static +bool flecs_rule_pred_neq_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + return flecs_rule_pred_match(op, redo, ctx, true); +} + +static +bool flecs_rule_pred_neq( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized neq operand"); + + ecs_table_range_t r = flecs_rule_get_range( + op, &op->second, EcsRuleSecond, ctx); + return flecs_rule_pred_neq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_pred_neq_name( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + const char *name = flecs_rule_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup_fullpath(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return true && !redo; + } + + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_neq_w_range(op, redo, ctx, r); +} + +static +bool flecs_rule_lookup( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + + const ecs_rule_t *rule = ctx->rule; + ecs_entity_t first = flecs_rule_var_get_entity(op->first.var, ctx); + ecs_rule_var_t *var = &rule->vars[op->src.var]; + + ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, + NULL, NULL, false); + if (!result) { + flecs_rule_var_set_entity(op, op->src.var, EcsWildcard, ctx); + return false; + } + + flecs_rule_var_set_entity(op, op->src.var, result, ctx); + + return true; +} + +static +bool flecs_rule_setvars( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + (void)op; + + const ecs_rule_t *rule = ctx->rule; + const ecs_filter_t *filter = &rule->filter; + ecs_var_id_t *src_vars = rule->src_vars; + ecs_iter_t *it = ctx->it; + + if (redo) { + return false; + } + + int32_t i; + ecs_flags32_t source_set = *ctx->source_set; + for (i = 0; i < filter->field_count; i ++) { + ecs_var_id_t var_id = src_vars[i]; + if (!var_id) { + continue; + } + + if (source_set & (1u << i)) { + continue; + } + + it->sources[i] = flecs_rule_var_get_entity(var_id, ctx); + } + + return true; +} + +static +bool flecs_rule_setthis( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); + ecs_var_t *vars = ctx->vars; + ecs_var_t *this_var = &vars[op->first.var]; + + if (!redo) { + /* Save values so we can restore them later */ + op_ctx->range = vars[0].range; + + /* Constrain This table variable to a single entity from the table */ + vars[0].range = flecs_range_from_entity(this_var->entity, ctx); + vars[0].entity = this_var->entity; + return true; + } else { + /* Restore previous values, so that instructions that are operating on + * the table variable use all the entities in the table. */ + vars[0].range = op_ctx->range; + vars[0].entity = 0; + return false; + } +} + +static +bool flecs_rule_setfixed( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + (void)op; + const ecs_rule_t *rule = ctx->rule; + const ecs_filter_t *filter = &rule->filter; + ecs_iter_t *it = ctx->it; + + if (redo) { + return false; + } + + int32_t i; + for (i = 0; i < filter->term_count; i ++) { + ecs_term_t *term = &filter->terms[i]; + ecs_term_id_t *src = &term->src; + if (src->flags & EcsIsEntity) { + it->sources[term->field_index] = src->id; + } + } + + return true; +} + +static +bool flecs_rule_setids( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + (void)op; + const ecs_rule_t *rule = ctx->rule; + const ecs_filter_t *filter = &rule->filter; + ecs_iter_t *it = ctx->it; + + if (redo) { + return false; + } + + int32_t i; + for (i = 0; i < filter->term_count; i ++) { + ecs_term_t *term = &filter->terms[i]; + it->ids[term->field_index] = term->id; + } + + return true; +} + +static +bool flecs_rule_setid( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + + ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); + ctx->it->ids[op->field_index] = op->first.entity; + return true; +} + +/* Check if entity is stored in table */ +static +bool flecs_rule_contain( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + + ecs_var_id_t src_id = op->src.var; + ecs_var_id_t first_id = op->first.var; + + ecs_table_t *table = flecs_rule_var_get_table(src_id, ctx); + + ecs_entity_t e = flecs_rule_var_get_entity(first_id, ctx); + return table == ecs_get_table(ctx->world, e); +} + +/* Check if first and second id of pair from last operation are the same */ +static +bool flecs_rule_pair_eq( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + + ecs_iter_t *it = ctx->it; + ecs_id_t id = it->ids[op->field_index]; + return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); +} + +static +bool flecs_rule_select_or( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_rule_iter_t *rit = &it->priv.iter.rule; + ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + + ecs_rule_lbl_t first = flecs_itolbl(ctx->op_index + 1); + if (!redo) { + op_ctx->op_index = first; + } + + const ecs_rule_op_t *cur = &rit->ops[op_ctx->op_index]; + bool result = false; + + /* Evaluate operations in OR chain one by one. */ + + do { + ctx->op_index = op_ctx->op_index; + ctx->written[op_ctx->op_index] = op->written; + + result = flecs_rule_dispatch(cur, redo, ctx); + if (result) { + ctx->written[op_ctx->op_index] |= cur->written; + + /* If a previous operation in the OR chain returned a result for the + * same matched source, skip it so we don't yield for each matching + * element in the chain. */ + ecs_rule_lbl_t prev_index; + for (prev_index = first; prev_index < op_ctx->op_index; + prev_index ++) + { + const ecs_rule_op_t *prev = &rit->ops[prev_index]; + ctx->op_index = prev_index; + ctx->written[prev_index] = ctx->written[op_ctx->op_index]; + if (flecs_rule_dispatch(prev, false, ctx)) { + break; + } + } + + if (prev_index != op_ctx->op_index) { + /* Duplicate match was found, redo search */ + redo = true; + continue; + } + break; + } + + /* No result was found, go to next operation in chain */ + op_ctx->op_index ++; + cur = &rit->ops[op_ctx->op_index]; + redo = false; + } while (cur->kind != EcsRuleEnd); + + return result; +} + +static +bool flecs_rule_run_block( + bool redo, + ecs_rule_run_ctx_t *ctx, + ecs_rule_ctrl_ctx_t *op_ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_rule_iter_t *rit = &it->priv.iter.rule; + + if (!redo) { + op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); + } else if (ctx->rit->ops[op_ctx->op_index].kind == EcsRuleEnd) { + return false; + } + + ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; + + return flecs_rule_run_until( + redo, ctx, rit->ops, ctx->op_index, op_ctx->op_index, EcsRuleEnd); +} + +static +bool flecs_rule_with_or( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + + bool result = flecs_rule_run_block(redo, ctx, op_ctx); + if (result) { + /* If a match was found, no need to keep searching for this source */ + op_ctx->op_index = op->next; + } + + return result; +} + +static +bool flecs_rule_or( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (op->flags & (EcsRuleIsVar << EcsRuleSrc)) { + uint64_t written = ctx->written[ctx->op_index]; + if (!(written & (1ull << op->src.var))) { + return flecs_rule_select_or(op, redo, ctx); + } + } + + return flecs_rule_with_or(op, redo, ctx); +} + +static +bool flecs_rule_run_block_w_reset( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + bool result = flecs_rule_run_block(redo, ctx, op_ctx); + if (!result) { + flecs_rule_reset_after_block(op, ctx, op_ctx); + } + return result; +} + +static +bool flecs_rule_not( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (redo) { + return false; + } + + return !flecs_rule_run_block_w_reset(op, redo, ctx); +} + +static +bool flecs_rule_optional( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + bool result = flecs_rule_run_block_w_reset(op, redo, ctx); + if (!redo) { + return true; /* Return at least once */ + } else { + return result; + } +} + +static +bool flecs_rule_eval_if( + const ecs_rule_op_t *op, + ecs_rule_run_ctx_t *ctx, + const ecs_rule_ref_t *ref, + ecs_flags16_t ref_kind) +{ + if (flecs_rule_ref_flags(op->flags, ref_kind) == EcsRuleIsVar) { + if (ctx->vars[ref->var].entity == EcsWildcard) { + ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + flecs_rule_reset_after_block(op, ctx, op_ctx); + return false; + } + } + return true; +} + +static +bool flecs_rule_if( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (!redo) { + if (!flecs_rule_eval_if(op, ctx, &op->src, EcsRuleSrc) || + !flecs_rule_eval_if(op, ctx, &op->first, EcsRuleFirst) || + !flecs_rule_eval_if(op, ctx, &op->second, EcsRuleSecond)) + { + return true; + } + } + + return flecs_rule_run_block_w_reset(op, redo, ctx); +} + +static +bool flecs_rule_end( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + (void)op; (void)ctx; + return !redo; +} + +static +bool flecs_rule_populate( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + (void)op; + if (!redo) { + ecs_iter_t *it = ctx->it; + if (it->flags & EcsIterNoData) { + return true; + } + + ECS_BIT_CLEAR(it->flags, EcsIterHasShared); + + const ecs_rule_t *rule = ctx->rule; + const ecs_filter_t *filter = &rule->filter; + int32_t i, field_count = filter->field_count; + ecs_flags64_t data_fields = filter->data_fields; + ecs_table_range_t *range = &ctx->vars[0].range; + ecs_table_t *table = range->table; + if (table && !range->count) { + range->count = ecs_table_count(table); + } + + for (i = 0; i < field_count; i ++) { + if (!(data_fields & (1llu << i))) { + continue; + } + + int32_t index = it->columns[i]; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + if (!index) { + continue; + } + + ecs_entity_t src = it->sources[i]; + if (!src) { + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (range->count && table->column_map) { + int32_t column = table->column_map[index - 1]; + if (column != -1) { + it->ptrs[i] = ECS_ELEM( + table->data.columns[column].data.array, + it->sizes[i], + range->offset); + continue; + } + } + } else { + ecs_record_t *r = flecs_entities_get(ctx->world, src); + ecs_table_t *src_table = r->table; + if (src_table->column_map) { + int32_t column = src_table->column_map[index - 1]; + if (column != -1) { + it->ptrs[i] = ecs_vec_get( + &src_table->data.columns[column].data, + it->sizes[i], + ECS_RECORD_TO_ROW(r->row)); + ECS_BIT_SET(it->flags, EcsIterHasShared); + continue; + } + } + } + } + + return true; + } else { + return false; + } +} + +static +bool flecs_rule_populate_self( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + (void)op; + if (!redo) { + const ecs_rule_t *rule = ctx->rule; + const ecs_filter_t *filter = &rule->filter; + int32_t i, field_count = filter->field_count; + ecs_flags64_t data_fields = filter->data_fields; + ecs_iter_t *it = ctx->it; + + ecs_table_range_t *range = &ctx->vars[0].range; + ecs_table_t *table = range->table; + if (!table->column_map) { + return true; + } + + if (!ecs_table_count(table)) { + return true; + } + + for (i = 0; i < field_count; i ++) { + if (!(data_fields & (1llu << i))) { + continue; + } + + int32_t index = it->columns[i]; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Only owned */ + if (!index) { + continue; + } + + int32_t column = table->column_map[index - 1]; + if (column != -1) { + it->ptrs[i] = ECS_ELEM( + table->data.columns[column].data.array, + it->sizes[i], + range->offset); + } + } + + return true; + } else { + return false; + } +} + +static +bool flecs_rule_dispatch( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + switch(op->kind) { + case EcsRuleAnd: return flecs_rule_and(op, redo, ctx); + case EcsRuleAndId: return flecs_rule_and_id(op, redo, ctx); + case EcsRuleAndAny: return flecs_rule_and_any(op, redo, ctx); + case EcsRuleTriv: return flecs_rule_triv(op, redo, ctx); + case EcsRuleTrivData: return flecs_rule_triv_data(op, redo, ctx); + case EcsRuleTrivWildcard: return flecs_rule_triv_wildcard(op, redo, ctx); + case EcsRuleSelectAny: return flecs_rule_select_any(op, redo, ctx); + case EcsRuleUp: return flecs_rule_up(op, redo, ctx); + case EcsRuleUpId: return flecs_rule_up_id(op, redo, ctx); + case EcsRuleSelfUp: return flecs_rule_self_up(op, redo, ctx); + case EcsRuleSelfUpId: return flecs_rule_self_up_id(op, redo, ctx); + case EcsRuleWith: return flecs_rule_with(op, redo, ctx); + case EcsRuleTrav: return flecs_rule_trav(op, redo, ctx); + case EcsRuleIds: return flecs_rule_ids(op, redo, ctx); + case EcsRuleIdsRight: return flecs_rule_idsright(op, redo, ctx); + case EcsRuleIdsLeft: return flecs_rule_idsleft(op, redo, ctx); + case EcsRuleEach: return flecs_rule_each(op, redo, ctx); + case EcsRuleStore: return flecs_rule_store(op, redo, ctx); + case EcsRuleReset: return flecs_rule_reset(op, redo, ctx); + case EcsRuleOr: return flecs_rule_or(op, redo, ctx); + case EcsRuleOptional: return flecs_rule_optional(op, redo, ctx); + case EcsRuleIf: return flecs_rule_if(op, redo, ctx); + case EcsRuleEnd: return flecs_rule_end(op, redo, ctx); + case EcsRuleNot: return flecs_rule_not(op, redo, ctx); + case EcsRulePredEq: return flecs_rule_pred_eq(op, redo, ctx); + case EcsRulePredNeq: return flecs_rule_pred_neq(op, redo, ctx); + case EcsRulePredEqName: return flecs_rule_pred_eq_name(op, redo, ctx); + case EcsRulePredNeqName: return flecs_rule_pred_neq_name(op, redo, ctx); + case EcsRulePredEqMatch: return flecs_rule_pred_eq_match(op, redo, ctx); + case EcsRulePredNeqMatch: return flecs_rule_pred_neq_match(op, redo, ctx); + case EcsRuleLookup: return flecs_rule_lookup(op, redo, ctx); + case EcsRuleSetVars: return flecs_rule_setvars(op, redo, ctx); + case EcsRuleSetThis: return flecs_rule_setthis(op, redo, ctx); + case EcsRuleSetFixed: return flecs_rule_setfixed(op, redo, ctx); + case EcsRuleSetIds: return flecs_rule_setids(op, redo, ctx); + case EcsRuleSetId: return flecs_rule_setid(op, redo, ctx); + case EcsRuleContain: return flecs_rule_contain(op, redo, ctx); + case EcsRulePairEq: return flecs_rule_pair_eq(op, redo, ctx); + case EcsRulePopulate: return flecs_rule_populate(op, redo, ctx); + case EcsRulePopulateSelf: return flecs_rule_populate_self(op, redo, ctx); + case EcsRuleYield: return false; + case EcsRuleNothing: return false; + } + return false; +} + +static +bool flecs_rule_run_until( + bool redo, + ecs_rule_run_ctx_t *ctx, + const ecs_rule_op_t *ops, + ecs_rule_lbl_t first, + ecs_rule_lbl_t cur, + ecs_rule_op_kind_t until) +{ + ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); + + ctx->op_index = cur; + const ecs_rule_op_t *op = &ops[ctx->op_index]; + ecs_assert(op->kind != until, ECS_INTERNAL_ERROR, NULL); + + do { + #ifdef FLECS_DEBUG + ctx->rit->profile[ctx->op_index].count[redo] ++; + #endif + + bool result = flecs_rule_dispatch(op, redo, ctx); + cur = (&op->prev)[result]; + redo = cur < ctx->op_index; + + if (!redo) { + ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; + } + + ctx->op_index = cur; + op = &ops[ctx->op_index]; + + if (cur <= first) { + return false; + } + if (op->kind == until) { + return true; + } + } while (true); + + return false; +} + +static +void flecs_rule_iter_init( + ecs_rule_run_ctx_t *ctx) +{ + ecs_assert(ctx->written != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t *it = ctx->it; + + const ecs_rule_t *rule = ctx->rule; + ecs_flags64_t it_written = it->constrained_vars; + ctx->written[0] = it_written; + if (it_written && ctx->rule->src_vars) { + /* If variables were constrained, check if there are any table + * variables that have a constrained entity variable. */ + ecs_var_t *vars = ctx->vars; + int32_t i, count = rule->filter.field_count; + for (i = 0; i < count; i ++) { + ecs_var_id_t var_id = rule->src_vars[i]; + ecs_rule_var_t *var = &rule->vars[var_id]; + + if (!(it_written & (1ull << var_id)) || + (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) + { + continue; + } + + /* Initialize table variable with constrained entity variable */ + ecs_var_t *tvar = &vars[var->table_id]; + tvar->range = flecs_range_from_entity(vars[var_id].entity, ctx); + ctx->written[0] |= (1ull << var->table_id); /* Mark as written */ + } + } + + ecs_flags32_t flags = rule->filter.flags; + if (flags & EcsFilterIsTrivial) { + if ((flags & EcsFilterMatchOnlySelf) || + !flecs_table_cache_count(&ctx->world->idr_isa_wildcard->cache)) + { + if (it_written) { + it->offset = ctx->vars[0].range.offset; + it->count = ctx->vars[0].range.count; + if (!it->count) { + ecs_assert(!it->offset, ECS_INVALID_PARAMETER, NULL); + it->count = ecs_table_count(ctx->vars[0].range.table); + } + + it->flags |= EcsIterTrivialTest; + flecs_rule_setids(&rule->ops[0], false, ctx); + } else { + if (flags & EcsFilterHasWildcards) { + it->flags |= EcsIterTrivialSearchWildcard; + flecs_rule_setids(&rule->ops[0], false, ctx); + } else if (flags & EcsFilterNoData) { + it->flags |= EcsIterTrivialSearchNoData; + flecs_rule_setids(&rule->ops[0], false, ctx); + } else { + it->flags |= EcsIterTrivialSearch; + flecs_rule_setids(&rule->ops[0], false, ctx); + } + } + } + } + + flecs_iter_validate(it); +} + +bool ecs_rule_next_instanced( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); + + ecs_rule_iter_t *rit = &it->priv.iter.rule; + ecs_rule_run_ctx_t ctx; + ctx.world = it->real_world; + ctx.rule = rit->rule; + ctx.it = it; + ctx.vars = rit->vars; + ctx.rule_vars = rit->rule_vars; + ctx.written = rit->written; + ctx.op_ctx = rit->op_ctx; + ctx.source_set = &rit->source_set; + ctx.rit = rit; + const ecs_rule_op_t *ops = rit->ops; + + bool redo = true; + if (!(it->flags & EcsIterIsValid)) { + ecs_assert(ctx.rule != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_rule_iter_init(&ctx); + redo = false; + } else { + it->frame_offset += it->count; + } + + /* Specialized iterator modes for trivial queries */ + if (it->flags & EcsIterTrivialSearch) { + ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + int32_t fields = ctx.rule->filter.term_count; + if (!flecs_rule_trivial_search(ctx.rule, &ctx, op_ctx, !redo, fields)) { + goto done; + } + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = flecs_table_entities_array(it->table); + return true; + } else if (it->flags & EcsIterTrivialSearchNoData) { + ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + int32_t fields = ctx.rule->filter.term_count; + if (!flecs_rule_trivial_search_nodata(ctx.rule, &ctx, op_ctx, !redo, fields)) { + goto done; + } + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = flecs_table_entities_array(it->table); + return true; + } else if (it->flags & EcsIterTrivialTest) { + int32_t fields = ctx.rule->filter.term_count; + if (!flecs_rule_trivial_test(ctx.rule, &ctx, !redo, fields)) { + goto done; + } + return true; + } else if (it->flags & EcsIterTrivialSearchWildcard) { + ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + int32_t fields = ctx.rule->filter.term_count; + if (!flecs_rule_trivial_search_w_wildcards(ctx.rule, &ctx, op_ctx, !redo, fields)) { + goto done; + } + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = flecs_table_entities_array(it->table); + return true; + } + + /* Default iterator mode */ + if (flecs_rule_run_until(redo, &ctx, ops, -1, rit->op, EcsRuleYield)) { + ecs_assert(ops[ctx.op_index].kind == EcsRuleYield, + ECS_INTERNAL_ERROR, NULL); + ecs_table_range_t *range = &ctx.vars[0].range; + ecs_table_t *table = range->table; + int32_t count = range->count; + if (table) { + if (!count) { + count = ecs_table_count(table); + } + it->table = table; + it->offset = range->offset; + it->count = count; + it->entities = ECS_ELEM_T( + table->data.entities.array, ecs_entity_t, it->offset); + } else if (count == 1) { + it->count = 1; + it->entities = &ctx.vars[0].entity; + } + + rit->op = flecs_itolbl(ctx.op_index - 1); + return true; + } + +done: + ecs_iter_fini(it); + return false; +} + +bool ecs_rule_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); +error: + return false; +} + +static +void flecs_rule_iter_fini_ctx( + ecs_iter_t *it, + ecs_rule_iter_t *rit) +{ + const ecs_rule_t *rule = rit->rule; + int32_t i, count = rule->op_count; + ecs_rule_op_t *ops = rule->ops; + ecs_rule_op_ctx_t *ctx = rit->op_ctx; + ecs_allocator_t *a = flecs_rule_get_allocator(it); + + for (i = 0; i < count; i ++) { + ecs_rule_op_t *op = &ops[i]; + switch(op->kind) { + case EcsRuleTrav: + flecs_rule_trav_cache_fini(a, &ctx[i].is.trav.cache); + break; + case EcsRuleUp: + case EcsRuleSelfUp: + case EcsRuleUpId: + case EcsRuleSelfUpId: { + ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; + if (cache->dir == EcsDown) { + flecs_rule_down_cache_fini(a, cache); + } else { + flecs_rule_up_cache_fini(cache); + } + break; + } + default: + break; + } + } +} + +static +void flecs_rule_iter_fini( + ecs_iter_t *it) +{ + ecs_rule_iter_t *rit = &it->priv.iter.rule; + ecs_assert(rit->rule != NULL, ECS_INVALID_OPERATION, NULL); + ecs_poly_assert(rit->rule, ecs_rule_t); + int32_t op_count = rit->rule->op_count; + int32_t var_count = rit->rule->var_count; + +#ifdef FLECS_DEBUG + if (it->flags & EcsIterProfile) { + char *str = ecs_rule_str_w_profile(rit->rule, it); + printf("%s\n", str); + ecs_os_free(str); + } + + flecs_iter_free_n(rit->profile, ecs_rule_op_profile_t, op_count); +#endif + + flecs_rule_iter_fini_ctx(it, rit); + flecs_iter_free_n(rit->vars, ecs_var_t, var_count); + flecs_iter_free_n(rit->written, ecs_write_flags_t, op_count); + flecs_iter_free_n(rit->op_ctx, ecs_rule_op_ctx_t, op_count); + rit->vars = NULL; + rit->written = NULL; + rit->op_ctx = NULL; + rit->rule = NULL; +} + +ecs_iter_t ecs_rule_iter( + const ecs_world_t *world, + const ecs_rule_t *rule) +{ + ecs_iter_t it = {0}; + ecs_rule_iter_t *rit = &it.priv.iter.rule; + ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_run_aperiodic(rule->filter.world, EcsAperiodicEmptyTables); + + int32_t i, var_count = rule->var_count, op_count = rule->op_count; + it.world = ECS_CONST_CAST(ecs_world_t*, world); + it.real_world = rule->filter.world; + it.query = &rule->filter; + it.terms = rule->filter.terms; + it.next = ecs_rule_next; + it.fini = flecs_rule_iter_fini; + it.field_count = rule->filter.field_count; + it.sizes = rule->filter.sizes; + it.system = rule->filter.entity; + flecs_filter_apply_iter_flags(&it, &rule->filter); + + flecs_iter_init(world, &it, + flecs_iter_cache_ids | + flecs_iter_cache_columns | + flecs_iter_cache_sources | + flecs_iter_cache_ptrs); + + rit->rule = rule; + rit->rule_vars = rule->vars; + rit->ops = rule->ops; + rit->source_set = 0; + if (var_count) { + rit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); + } + if (op_count) { + rit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); + rit->op_ctx = flecs_iter_calloc_n(&it, ecs_rule_op_ctx_t, op_count); + } + +#ifdef FLECS_DEBUG + rit->profile = flecs_iter_calloc_n(&it, ecs_rule_op_profile_t, op_count); +#endif + + for (i = 1; i < var_count; i ++) { + rit->vars[i].entity = EcsWildcard; + } + + it.variables = rit->vars; + it.variable_count = rule->var_pub_count; + it.variable_names = rule->var_names; + +error: + return it; +} + +#endif + +/** + * @file addons/rules/trav_cache.c + * @brief Cache that stores the result of graph traversal. + */ + + +#ifdef FLECS_RULES + +static +void flecs_rule_build_down_cache( + ecs_world_t *world, + ecs_allocator_t *a, + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); + if (!idr) { + return; + } + + ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + elem->entity = entity; + elem->idr = idr; + + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } + + int32_t i, count = ecs_table_count(table); + ecs_entity_t *entities = table->data.entities.array; + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if (r->row & EcsEntityIsTraversable) { + flecs_rule_build_down_cache( + world, a, ctx, cache, trav, entities[i]); + } + } + } + } +} + +static +void flecs_rule_build_up_cache( + ecs_world_t *world, + ecs_allocator_t *a, + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table, + const ecs_table_record_t *tr, + int32_t root_column) +{ + ecs_id_t *ids = table->type.array; + int32_t i = tr->index, end = i + tr->count; + bool is_root = root_column == -1; + + for (; i < end; i ++) { + ecs_entity_t second = ecs_pair_second(world, ids[i]); + if (is_root) { + root_column = i; + } + + ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + el->entity = second; + el->column = root_column; + el->idr = NULL; + + ecs_record_t *r = flecs_entities_get_any(world, second); + if (r->table) { + ecs_table_record_t *r_tr = flecs_id_record_get_table( + cache->idr, r->table); + if (!r_tr) { + return; + } + flecs_rule_build_up_cache(world, a, ctx, cache, trav, r->table, + r_tr, root_column); + } + } +} + +void flecs_rule_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache) +{ + ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); +} + +void flecs_rule_get_trav_down_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) +{ + if (cache->id != ecs_pair(trav, entity) || cache->up) { + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_rule_build_down_cache(world, a, ctx, cache, trav, entity); + cache->id = ecs_pair(trav, entity); + cache->up = false; + } +} + +void flecs_rule_get_trav_up_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); + + ecs_id_record_t *idr = cache->idr; + if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { + idr = cache->idr = flecs_id_record_get(world, + ecs_pair(trav, EcsWildcard)); + if (!idr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; + } + } + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; + } + + ecs_id_t id = table->type.array[tr->index]; + + if (cache->id != id || !cache->up) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_rule_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); + cache->id = id; + cache->up = true; + } +} + +#endif + + +#ifdef FLECS_RULES + +static +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_with, + bool self); + +static +ecs_trav_down_t* flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_trav, + ecs_id_record_t *idr_with, + bool self); + +static +ecs_trav_down_t* flecs_trav_down_ensure( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t entity) +{ + ecs_trav_down_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_down_t, entity); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); + ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); + } + + return trav[0]; +} + +static +ecs_trav_down_t* flecs_trav_table_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + const ecs_table_t *table, + ecs_id_record_t *idr_with, + bool self) +{ + ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); + + if (!table->_->traversable_count) { + return dst; + } + + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t entity = entities[i]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (!record) { + continue; + } + + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_pair(trav, entity)); + if (!idr_trav) { + continue; + } + + flecs_trav_entity_down(world, a, cache, dst, + trav, entity, idr_trav, idr_with, self); + } + } + + return dst; +} + +static +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_with, + bool self) +{ + if (trav == EcsIsA || !world->idr_isa_wildcard) { + return; + } + + ecs_id_record_t *idr_isa = flecs_id_record_get( + world, ecs_pair(EcsIsA, entity)); + if (!idr_isa) { + return; + } + + ecs_table_cache_iter_t it; + if (flecs_table_cache_all_iter(&idr_isa->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } + + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *record = flecs_entities_get(world, e); + if (!record) { + continue; + } + + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_pair(trav, e)); + flecs_trav_entity_down(world, a, cache, dst, trav, e, + idr_trav, idr_with, self); + } + } + } + } +} + +static +ecs_trav_down_t* flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_id_record_t *idr_trav, + ecs_id_record_t *idr_with, + bool self) +{ + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_trav_entity_down_isa( + world, a, cache, dst, trav, entity, idr_with, self); + + int32_t first = ecs_vec_count(&dst->elems); + + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&idr_trav->cache, &it)) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + bool leaf = false; + + if (flecs_id_record_get_table(idr_with, table) != NULL) { + if (self) { + continue; + } + leaf = true; + } + + /* If record is not the first instance of (trav, *), don't add it + * to the cache. */ + int32_t index = tr->index; + if (index) { + ecs_id_t id = table->type.array[index - 1]; + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { + int32_t col = ecs_search_relation(world, table, 0, + idr_with->id, trav, EcsUp, NULL, NULL, &tr); + ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); + + if (col != index) { + /* First relationship through which the id is + * reachable is not the current one, so skip. */ + continue; + } + } + } + + ecs_trav_down_elem_t *elem = ecs_vec_append_t( + a, &dst->elems, ecs_trav_down_elem_t); + elem->table = table; + elem->leaf = leaf; + } + } + + /* Breadth first walk */ + int32_t t, last = ecs_vec_count(&dst->elems); + for (t = first; t < last; t ++) { + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &dst->elems, ecs_trav_down_elem_t, t); + if (!elem->leaf) { + flecs_trav_table_down(world, a, cache, dst, trav, + elem->table, idr_with, self); + } + } + + return dst; +} + +ecs_trav_down_t* flecs_rule_get_down_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t e, + ecs_id_record_t *idr_with, + bool self) +{ + ecs_world_t *world = ctx->it->real_world; + ecs_assert(!(cache->dir & EcsUp), ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsDown; + + ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); + + ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); + if (result->ready) { + return result; + } + + ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); + if (!idr_trav) { + if (trav != EcsIsA) { + flecs_trav_entity_down_isa( + world, a, cache, result, trav, e, idr_with, self); + } + result->ready = true; + return result; + } + + ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); + flecs_trav_entity_down( + world, a, cache, result, trav, e, idr_trav, idr_with, self); + result->ready = true; + + return result; +} + +void flecs_rule_down_cache_fini( + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache) +{ + ecs_map_iter_t it = ecs_map_iter(&cache->src); + while (ecs_map_next(&it)) { + ecs_trav_down_t *t = ecs_map_ptr(&it); + ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); + } + ecs_map_fini(&cache->src); +} + +#endif + + +#ifdef FLECS_RULES + +static +ecs_trav_up_t* flecs_trav_up_ensure( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + uint64_t table_id) +{ + ecs_trav_up_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_up_t, table_id); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); + } + + return trav[0]; +} + +static +int32_t flecs_trav_type_search( + ecs_trav_up_t *up, + const ecs_table_t *table, + ecs_id_record_t *idr_with, + ecs_type_t *type) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); + if (tr) { + up->id = type->array[tr->index]; + return up->column = tr->index; + } + + return -1; +} + +static +int32_t flecs_trav_type_offset_search( + ecs_trav_up_t *up, + int32_t offset, + ecs_id_t with, + ecs_type_t *type) +{ + ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); + + while (offset < type->count) { + ecs_id_t type_id = type->array[offset ++]; + if (ecs_id_match(type_id, with)) { + up->id = flecs_to_public_id(type_id); + return up->column = offset - 1; + } + } + + return -1; +} + +static +ecs_trav_up_t* flecs_trav_table_up( + const ecs_rule_run_ctx_t *ctx, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + const ecs_world_t *world, + ecs_entity_t src, + ecs_id_t with, + ecs_id_t rel, + ecs_id_record_t *idr_with, + ecs_id_record_t *idr_trav) +{ + ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); + if (up->ready) { + return up; + } + + ecs_record_t *src_record = flecs_entities_get_any(world, src); + ecs_table_t *table = src_record->table; + if (!table) { + goto not_found; + } + + ecs_type_t type = table->type; + if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { + up->src = src; + goto found; + } + + ecs_flags32_t flags = table->flags; + if ((flags & EcsTableHasPairs) && rel) { + bool is_a = idr_trav == world->idr_isa_wildcard; + if (is_a) { + if (!(flags & EcsTableHasIsA)) { + goto not_found; + } + + if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { + goto not_found; + } + } + + ecs_trav_up_t up_pair = {0}; + int32_t r_column = flecs_trav_type_search( + &up_pair, table, idr_trav, &type); + + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, idr_with, idr_trav); + if (up_parent->column != -1) { + up->src = up_parent->src; + up->column = up_parent->column; + up->id = up_parent->id; + goto found; + } + + r_column = flecs_trav_type_offset_search( + &up_pair, r_column + 1, rel, &type); + } + + if (!is_a) { + idr_trav = world->idr_isa_wildcard; + r_column = flecs_trav_type_search( + &up_pair, table, idr_trav, &type); + + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, idr_with, idr_trav); + if (up_parent->column != -1) { + up->src = up_parent->src; + up->column = up_parent->column; + up->id = up_parent->id; + goto found; + } + + r_column = flecs_trav_type_offset_search( + &up_pair, r_column + 1, rel, &type); + } + } + } + +not_found: + up->column = -1; +found: + up->ready = true; + return up; +} + +ecs_trav_up_t* flecs_rule_get_up_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_table_t *table, + ecs_id_t with, + ecs_entity_t trav, + ecs_id_record_t *idr_with, + ecs_id_record_t *idr_trav) +{ + if (cache->with && cache->with != with) { + flecs_rule_up_cache_fini(cache); + } + + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); + + ecs_assert(!(cache->dir & EcsDown), ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsUp; + cache->with = with; + + ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); + if (!tr) { + return NULL; /* Table doesn't have the relationship */ + } + + int32_t i = tr->index, end = i + tr->count; + for (; i < end; i ++) { + ecs_id_t id = table->type.array[i]; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, + with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + if (result->src != 0) { + return result; + } + } + + return NULL; +} + +void flecs_rule_up_cache_fini( + ecs_trav_up_cache_t *cache) +{ + ecs_map_fini(&cache->src); +} + +#endif + +/** + * @file addons/rules/engine.c + * @brief Iterator for trivial queries. + */ + + +#ifdef FLECS_RULES + +static +bool flecs_rule_trivial_init( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + int32_t t, count = filter->term_count; + ecs_term_t *terms = filter->terms; + + for (t = 0; t < count; t ++) { + ecs_term_t *term = &terms[t]; + if (!term->idr) { + term->idr = flecs_id_record_get(world, term->id); + if (!term->idr) { + /* Id doesn't exist, so query can't match */ + return false; + } + } + } + + return true; +} + +bool flecs_rule_trivial_test( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + bool first, + int32_t term_count) +{ + if (first) { + const ecs_filter_t *filter = &rule->filter; + int32_t t; + ecs_term_t *terms = filter->terms; + ecs_iter_t *it = ctx->it; + + if (!flecs_rule_trivial_init(ctx->world, filter)) { + return false; + } + + ecs_table_t *table = ctx->vars[0].range.table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, NULL); + + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + const ecs_table_record_t *tr = flecs_id_record_get_table( + term->idr, table); + if (!tr) { + return false; + } + + it->columns[t] = tr->index + 1; + if (it->count && tr->column != -1) { + it->ptrs[t] = ecs_vec_get( + &table->data.columns[tr->column].data, + it->sizes[t], + it->offset); + } + } + + it->table = table; + it->entities = &flecs_table_entities_array(table)[it->offset]; + return true; + } else { + return false; + } +} + +static +bool flecs_rule_trivial_search_init( + const ecs_rule_run_ctx_t *ctx, + ecs_rule_trivial_ctx_t *op_ctx, + const ecs_filter_t *filter, + bool first) +{ + if (first) { + ecs_term_t *terms = filter->terms; + if (!flecs_rule_trivial_init(ctx->world, filter)) { + return false; + } + + if (filter->flags & EcsFilterMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&terms[0].idr->cache, &op_ctx->it)){ + return false; + } + } else { + if (!flecs_table_cache_iter(&terms[0].idr->cache, &op_ctx->it)) { + return false; + } + } + } + + return true; +} + +bool flecs_rule_trivial_search( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + ecs_rule_trivial_ctx_t *op_ctx, + bool first, + int32_t term_count) +{ + const ecs_filter_t *filter = &rule->filter; + int32_t t; + ecs_term_t *terms = filter->terms; + ecs_iter_t *it = ctx->it; + + if (!flecs_rule_trivial_search_init(ctx, op_ctx, filter, first)) { + return false; + } + + do { + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } + + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableIsPrefab|EcsTableIsDisabled)) { + continue; + } + + for (t = 1; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + const ecs_table_record_t *tr_with = flecs_id_record_get_table( + term->idr, table); + if (!tr_with) { + break; + } + + it->columns[t] = tr_with->index + 1; + if (tr_with->column != -1) { + it->ptrs[t] = ecs_vec_first( + &table->data.columns[tr_with->column].data); + } + } + + if (t == term_count) { + ctx->vars[0].range.table = table; + ctx->vars[0].range.count = 0; + ctx->vars[0].range.offset = 0; + it->columns[0] = tr->index + 1; + if (tr->column != -1) { + it->ptrs[0] = ecs_vec_first( + &table->data.columns[tr->column].data); + } + break; + } + } while (true); + + return true; +} + +bool flecs_rule_trivial_search_w_wildcards( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + ecs_rule_trivial_ctx_t *op_ctx, + bool first, + int32_t term_count) +{ + bool result = flecs_rule_trivial_search( + rule, ctx, op_ctx, first, term_count); + if (result) { + ecs_iter_t *it = ctx->it; + ecs_table_t *table = ctx->vars[0].range.table; + int32_t t; + for (t = 0; t < term_count; t ++) { + it->ids[t] = table->type.array[it->columns[t] - 1]; + } + } + + return result; +} + +bool flecs_rule_trivial_test_w_wildcards( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + bool first, + int32_t term_count) +{ + bool result = flecs_rule_trivial_test( + rule, ctx, first, term_count); + if (result) { + ecs_iter_t *it = ctx->it; + ecs_table_t *table = ctx->vars[0].range.table; + int32_t t; + for (t = 0; t < term_count; t ++) { + it->ids[t] = table->type.array[it->columns[t] - 1]; + } + } + + return result; +} + +bool flecs_rule_trivial_search_nodata( + const ecs_rule_t *rule, + const ecs_rule_run_ctx_t *ctx, + ecs_rule_trivial_ctx_t *op_ctx, + bool first, + int32_t term_count) +{ + const ecs_filter_t *filter = &rule->filter; + int32_t t; + ecs_term_t *terms = filter->terms; + ecs_iter_t *it = ctx->it; + + if (!flecs_rule_trivial_search_init(ctx, op_ctx, filter, first)) { + return false; + } + + do { + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } + + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableIsPrefab|EcsTableIsDisabled)) { + continue; + } + + for (t = 1; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + const ecs_table_record_t *tr_with = flecs_id_record_get_table( + term->idr, table); + if (!tr_with) { + break; + } + + it->columns[t] = tr_with->index + 1; + } + + if (t == term_count) { + ctx->vars[0].range.table = table; + ctx->vars[0].range.count = 0; + ctx->vars[0].range.offset = 0; + it->columns[0] = tr->index + 1; + break; + } + } while (true); + + return true; +} + +#endif + +/** + * @file addons/system/system.c + * @brief System addon. + */ + + +#ifdef FLECS_SYSTEM + + +ecs_mixins_t ecs_system_t_mixins = { + .type_name = "ecs_system_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_system_t, world), + [EcsMixinEntity] = offsetof(ecs_system_t, entity), + [EcsMixinDtor] = offsetof(ecs_system_t, dtor) + } +}; + +/* -- Public API -- */ + +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + ecs_system_t *system_data, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + int32_t offset, + int32_t limit, + void *param) +{ + ecs_ftime_t time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; + + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; + } + + 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; + } + } + + if (ecs_should_log_3()) { + char *path = ecs_get_fullpath(world, system); + ecs_dbg_3("worker %d: %s", stage_index, path); + ecs_os_free(path); + } + + ecs_time_t time_start; + bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); + if (measure_time) { + ecs_os_get_time(&time_start); + } + + ecs_world_t *thread_ctx = world; + if (stage) { + thread_ctx = stage->thread_ctx; + } else { + stage = &world->stages[0]; + } + + /* Prepare the query iterator */ + ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); + ecs_iter_t *it = &qit; + + qit.system = system; + qit.delta_time = delta_time; + qit.delta_system_time = time_elapsed; + qit.frame_offset = offset; + qit.param = param; + qit.ctx = system_data->ctx; + qit.binding_ctx = system_data->binding_ctx; + + flecs_defer_begin(world, stage); + + if (offset || limit) { + pit = ecs_page_iter(it, offset, limit); + it = &pit; + } + + if (stage_count > 1 && system_data->multi_threaded) { + wit = ecs_worker_iter(it, stage_index, stage_count); + it = &wit; + } + + ecs_iter_action_t action = system_data->action; + it->callback = action; + + ecs_run_action_t run = system_data->run; + if (run) { + run(it); + } else if (system_data->query->filter.term_count) { + if (it == &qit) { + while (ecs_query_next(&qit)) { + action(&qit); + } + } else { + while (ecs_iter_next(it)) { + action(it); + } + } + } else { + action(&qit); + ecs_iter_fini(&qit); + } + + if (measure_time) { + system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); + } + + system_data->invoke_count ++; + + flecs_defer_end(world, stage); + + return it->interrupted_by; +} + +/* -- Public API -- */ + +ecs_entity_t ecs_run_w_filter( + ecs_world_t *world, + ecs_entity_t system, + ecs_ftime_t delta_time, + int32_t offset, + int32_t limit, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, + offset, limit, param); +} + +ecs_entity_t ecs_run_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + return ecs_run_intern( + world, stage, system, system_data, stage_index, stage_count, + delta_time, 0, 0, param); +} + +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + ecs_ftime_t delta_time, + void *param) +{ + return ecs_run_w_filter(world, system, delta_time, 0, 0, param); +} + +ecs_query_t* ecs_system_get_query( + const ecs_world_t *world, + ecs_entity_t system) +{ + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->query; + } else { + return NULL; + } +} + +void* ecs_system_get_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->ctx; + } else { + return NULL; + } +} + +void* ecs_system_get_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->binding_ctx; + } else { + return NULL; + } +} + +/* System deinitialization */ +static +void flecs_system_fini(ecs_system_t *sys) { + if (sys->ctx_free) { + sys->ctx_free(sys->ctx); + } + + if (sys->binding_ctx_free) { + sys->binding_ctx_free(sys->binding_ctx); + } + + ecs_poly_free(sys, ecs_system_t); +} + +static +void flecs_system_init_timer( + ecs_world_t *world, + ecs_entity_t entity, + const ecs_system_desc_t *desc) +{ + if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || + ECS_NEQZERO(desc->tick_source)) + { +#ifdef FLECS_TIMER + if (ECS_NEQZERO(desc->interval)) { + ecs_set_interval(world, entity, desc->interval); + } + + if (desc->rate) { + ecs_entity_t tick_source = desc->tick_source; + if (!tick_source) { + tick_source = entity; + } + ecs_set_rate(world, entity, desc->rate, tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, entity, desc->tick_source); + } +#else + (void)world; + (void)entity; + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif + } +} + +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc) +{ + ecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); + + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_new(world, 0); + } + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_system_t); + if (!poly->poly) { + ecs_system_t *system = ecs_poly_new(ecs_system_t); + ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); + + poly->poly = system; + system->world = world; + system->dtor = (ecs_poly_dtor_t)flecs_system_fini; + system->entity = entity; + + ecs_query_desc_t query_desc = desc->query; + query_desc.filter.entity = entity; + + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, entity); + return 0; + } + + /* Prevent the system from moving while we're initializing */ + flecs_defer_begin(world, &world->stages[0]); + + system->query = query; + system->query_entity = query->filter.entity; + + system->run = desc->run; + system->action = desc->callback; + + system->ctx = desc->ctx; + system->binding_ctx = desc->binding_ctx; + + system->ctx_free = desc->ctx_free; + system->binding_ctx_free = desc->binding_ctx_free; + + system->tick_source = desc->tick_source; + + system->multi_threaded = desc->multi_threaded; + system->no_readonly = desc->no_readonly; + + flecs_system_init_timer(world, entity, desc); + + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]system#[reset] %s created", + ecs_get_name(world, entity)); + } + + ecs_defer_end(world); + } else { + ecs_poly_assert(poly->poly, ecs_system_t); + ecs_system_t *system = (ecs_system_t*)poly->poly; + + if (desc->run) { + system->run = desc->run; + } + if (desc->callback) { + system->action = desc->callback; + } + + if (system->ctx_free) { + if (system->ctx && system->ctx != desc->ctx) { + system->ctx_free(system->ctx); + } + } + if (system->binding_ctx_free) { + if (system->binding_ctx && system->binding_ctx != desc->binding_ctx) { + system->binding_ctx_free(system->binding_ctx); + } + } + + if (desc->ctx) { + system->ctx = desc->ctx; + } + if (desc->binding_ctx) { + system->binding_ctx = desc->binding_ctx; + } + if (desc->ctx_free) { + system->ctx_free = desc->ctx_free; + } + if (desc->binding_ctx_free) { + system->binding_ctx_free = desc->binding_ctx_free; + } + if (desc->query.filter.instanced) { + ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced); + } + if (desc->multi_threaded) { + system->multi_threaded = desc->multi_threaded; + } + if (desc->no_readonly) { + system->no_readonly = desc->no_readonly; + } + + flecs_system_init_timer(world, entity, desc); + } + + ecs_poly_modified(world, entity, ecs_system_t); + + return entity; +error: + return 0; +} + +void FlecsSystemImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsSystem); +#ifdef FLECS_COREDOC + ECS_IMPORT(world, FlecsDoc); + ecs_doc_set_brief(world, ecs_id(FlecsSystem), + "Module that implements Flecs systems"); +#endif + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_tag(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); + + /* Make sure to never inherit system component. This makes sure that any + * term created for the System component will default to 'self' traversal, + * which improves efficiency of the query. */ + ecs_add_id(world, EcsSystem, EcsDontInherit); +} + +#endif + diff --git a/libs/flecs/flecs.h b/libs/flecs/flecs.h new file mode 100644 index 0000000..dd0f6d4 --- /dev/null +++ b/libs/flecs/flecs.h @@ -0,0 +1,31842 @@ +// Comment out this line when using as DLL +#define flecs_STATIC +/** + * @file flecs.h + * @brief Flecs public API. + * + * This file contains the public API for Flecs. + */ + +#ifndef FLECS_H +#define FLECS_H + +/** + * @defgroup c C API + * + * @{ + * @} + */ + +/** + * @defgroup core Core + * @ingroup c + * Core ECS functionality (entities, storage, queries). + * + * @{ + */ + +/** + * @defgroup options API defines + * Defines for customizing compile time features. + * + * @{ + */ + +/** @def ecs_float_t + * Customizable precision for floating point operations */ +#ifndef ecs_float_t +#define ecs_float_t float +#endif + +/** @def ecs_ftime_t + * Customizable precision for scalar time values. Change to double precision for + * processes that can run for a long time (e.g. longer than a day). */ +#ifndef ecs_ftime_t +#define ecs_ftime_t ecs_float_t +#endif + +/** @def FLECS_LEGACY + * Define when building for C89 + */ +// #define FLECS_LEGACY + +/** @def FLECS_NO_DEPRECATED_WARNINGS + * disables deprecated warnings + */ +#define FLECS_NO_DEPRECATED_WARNINGS + +/** @def FLECS_ACCURATE_COUNTERS + * Define to ensure that global counters used for statistics (such as the + * allocation counters in the OS API) are accurate in multithreaded + * applications, at the cost of increased overhead. + */ +// #define FLECS_ACCURATE_COUNTERS + +/* Make sure provided configuration is valid */ +#if defined(FLECS_DEBUG) && defined(FLECS_NDEBUG) +#error "invalid configuration: cannot both define FLECS_DEBUG and FLECS_NDEBUG" +#endif +#if defined(FLECS_DEBUG) && defined(NDEBUG) +#error "invalid configuration: cannot both define FLECS_DEBUG and NDEBUG" +#endif + +/** @def FLECS_DEBUG + * Used for input parameter checking and cheap sanity checks. There are lots of + * asserts in every part of the code, so this will slow down applications. + */ +#if !defined(FLECS_DEBUG) && !defined(FLECS_NDEBUG) +#if defined(NDEBUG) +#define FLECS_NDEBUG +#else +#define FLECS_DEBUG +#endif +#endif + +/** @def FLECS_SANITIZE + * Enables expensive checks that can detect issues early. Recommended for + * running tests or when debugging issues. This will severely slow down code. + */ +#ifdef FLECS_SANITIZE +#ifndef FLECS_DEBUG +#define FLECS_DEBUG /* If sanitized mode is enabled, so is debug mode */ +#endif +#endif + +/* Tip: if you see weird behavior that you think might be a bug, make sure to + * test with the FLECS_DEBUG or FLECS_SANITIZE flags enabled. There's a good + * chance that this gives you more information about the issue! */ + +/** @def FLECS_SOFT_ASSERT + * Define to not abort for recoverable errors, like invalid parameters. An error + * is still thrown to the console. This is recommended for when running inside a + * third party runtime, such as the Unreal editor. + * + * Note that internal sanity checks (ECS_INTERNAL_ERROR) will still abort a + * process, as this gives more information than a (likely) subsequent crash. + * + * When a soft assert occurs, the code will attempt to minimize the number of + * side effects of the failed operation, but this may not always be possible. + * Even though an application may still be able to continue running after a soft + * assert, it should be treated as if in an undefined state. + */ +// #define FLECS_SOFT_ASSERT + +/** @def FLECS_KEEP_ASSERT + * By default asserts are disabled in release mode, when either FLECS_NDEBUG or + * NDEBUG is defined. Defining FLECS_KEEP_ASSERT ensures that asserts are not + * disabled. This define can be combined with FLECS_SOFT_ASSERT. + */ +// #define FLECS_KEEP_ASSERT + +/** @def FLECS_CUSTOM_BUILD + * This macro lets you customize which addons to build flecs with. + * Without any addons Flecs is just a minimal ECS storage, but addons add + * features such as systems, scheduling and reflection. If an addon is disabled, + * it is excluded from the build, so that it consumes no resources. By default + * all addons are enabled. + * + * You can customize a build by either whitelisting or blacklisting addons. To + * whitelist addons, first define the FLECS_CUSTOM_BUILD macro, which disables + * all addons. You can then manually select the addons you need by defining + * their macro, like "FLECS_SYSTEM". + * + * To blacklist an addon, make sure to *not* define FLECS_CUSTOM_BUILD, and + * instead define the addons you don't need by defining FLECS_NO_, for + * example "FLECS_NO_SYSTEM". If there are any addons that depend on the + * blacklisted addon, an error will be thrown during the build. + * + * Note that addons can have dependencies on each other. Addons will + * automatically enable their dependencies. To see the list of addons that was + * compiled in a build, enable tracing before creating the world by doing: + * + * @code + * ecs_log_set_level(0); + * @endcode + * + * which outputs the full list of addons Flecs was compiled with. + */ +// #define FLECS_CUSTOM_BUILD + +/** @def FLECS_CPP_NO_AUTO_REGISTRATION + * When set, the C++ API will require that components are registered before they + * are used. This is useful in multithreaded applications, where components need + * to be registered beforehand, and to catch issues in projects where component + * registration is mandatory. Disabling automatic component registration also + * slightly improves performance. + * The C API is not affected by this feature. + */ +// #define FLECS_CPP_NO_AUTO_REGISTRATION + +#ifndef FLECS_CUSTOM_BUILD +// #define FLECS_C /**< C API convenience macros, always enabled */ +#define FLECS_CPP /**< C++ API */ +#define FLECS_MODULE /**< Module support */ +#define FLECS_PARSER /**< String parser for queries */ +#define FLECS_PLECS /**< ECS data definition format */ +#define FLECS_RULES /**< Constraint solver for advanced queries */ +#define FLECS_SNAPSHOT /**< Snapshot & restore ECS data */ +#define FLECS_STATS /**< Access runtime statistics */ +#define FLECS_MONITOR /**< Track runtime statistics periodically */ +#define FLECS_METRICS /**< Expose component data as statistics */ +#define FLECS_ALERTS /**< Monitor conditions for errors */ +#define FLECS_SYSTEM /**< System support */ +#define FLECS_PIPELINE /**< Pipeline support */ +#define FLECS_TIMER /**< Timer support */ +#define FLECS_META /**< Reflection support */ +#define FLECS_META_C /**< Utilities for populating reflection data */ +#define FLECS_UNITS /**< Builtin standard units */ +#define FLECS_EXPR /**< Parsing strings to/from component values */ +#define FLECS_JSON /**< Parsing JSON to/from component values */ +#define FLECS_DOC /**< Document entities & components */ +#define FLECS_COREDOC /**< Documentation for core entities & components */ +#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ +#define FLECS_APP /**< Application addon */ +#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ +#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ +#define FLECS_REST /**< REST API for querying application data */ +// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ +#endif // ifndef FLECS_CUSTOM_BUILD + +/** @def FLECS_LOW_FOOTPRINT + * Set a number of constants to values that decrease memory footprint, at the + * cost of decreased performance. */ +// #define FLECS_LOW_FOOTPRINT +#ifdef FLECS_LOW_FOOTPRINT +#define FLECS_HI_COMPONENT_ID (16) +#define FLECS_HI_ID_RECORD_ID (16) +#define FLECS_SPARSE_PAGE_BITS (6) +#define FLECS_ENTITY_PAGE_BITS (6) +#define FLECS_USE_OS_ALLOC +#endif + +/** @def FLECS_HI_COMPONENT_ID + * This constant can be used to balance between performance and memory + * utilization. The constant is used in two ways: + * - Entity ids 0..FLECS_HI_COMPONENT_ID are reserved for component ids. + * - Used as lookup array size in table edges. + * + * Increasing this value increases the size of the lookup array, which allows + * fast table traversal, which improves performance of ECS add/remove + * operations. Component ids that fall outside of this range use a regular map + * lookup, which is slower but more memory efficient. */ +#ifndef FLECS_HI_COMPONENT_ID +#define FLECS_HI_COMPONENT_ID (256) +#endif + +/** @def FLECS_HI_ID_RECORD_ID + * This constant can be used to balance between performance and memory + * utilization. The constant is used to determine the size of the id record + * lookup array. Id values that fall outside of this range use a regular map + * lookup, which is slower but more memory efficient. + */ +#ifndef FLECS_HI_ID_RECORD_ID +#define FLECS_HI_ID_RECORD_ID (1024) +#endif + +/** @def FLECS_SPARSE_PAGE_BITS + * This constant is used to determine the number of bits of an id that is used + * to determine the page index when used with a sparse set. The number of bits + * determines the page size, which is (1 << bits). + * Lower values decrease memory utilization, at the cost of more allocations. */ +#ifndef FLECS_SPARSE_PAGE_BITS +#define FLECS_SPARSE_PAGE_BITS (12) +#endif + +/** @def FLECS_ENTITY_PAGE_BITS + * Same as FLECS_SPARSE_PAGE_BITS, but for the entity index. */ +#ifndef FLECS_ENTITY_PAGE_BITS +#define FLECS_ENTITY_PAGE_BITS (12) +#endif + +/** @def FLECS_USE_OS_ALLOC + * When enabled, Flecs will use the OS allocator provided in the OS API directly + * instead of the builtin block allocator. This can decrease memory utilization + * as memory will be freed more often, at the cost of decreased performance. */ +// #define FLECS_USE_OS_ALLOC + +/** @def FLECS_ID_DESC_MAX + * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ +#ifndef FLECS_ID_DESC_MAX +#define FLECS_ID_DESC_MAX (32) +#endif + +/** @def FLECS_TERM_DESC_MAX + * Maximum number of terms in ecs_filter_desc_t */ +#define FLECS_TERM_DESC_MAX (16) + +/** @def FLECS_EVENT_DESC_MAX + * Maximum number of events in ecs_observer_desc_t */ +#define FLECS_EVENT_DESC_MAX (8) + +/** @def FLECS_VARIABLE_COUNT_MAX + * Maximum number of query variables per query */ +#define FLECS_VARIABLE_COUNT_MAX (64) + +/** @def FLECS_QUERY_SCOPE_NESTING_MAX + * Maximum nesting depth of query scopes */ +#define FLECS_QUERY_SCOPE_NESTING_MAX (8) + +/** @} */ + +/** + * @file api_defines.h + * @brief Supporting defines for the public API. + * + * This file contains constants / macros 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 + +/** + * @file api_flags.h + * @brief Bitset flags used by internals. + */ + +#ifndef FLECS_API_FLAGS_H +#define FLECS_API_FLAGS_H + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// World flags +//////////////////////////////////////////////////////////////////////////////// + +#define EcsWorldQuitWorkers (1u << 0) +#define EcsWorldReadonly (1u << 1) +#define EcsWorldInit (1u << 2) +#define EcsWorldQuit (1u << 3) +#define EcsWorldFini (1u << 4) +#define EcsWorldMeasureFrameTime (1u << 5) +#define EcsWorldMeasureSystemTime (1u << 6) +#define EcsWorldMultiThreaded (1u << 7) + + +//////////////////////////////////////////////////////////////////////////////// +//// OS API flags +//////////////////////////////////////////////////////////////////////////////// + +#define EcsOsApiHighResolutionTimer (1u << 0) +#define EcsOsApiLogWithColors (1u << 1) +#define EcsOsApiLogWithTimeStamp (1u << 2) +#define EcsOsApiLogWithTimeDelta (1u << 3) + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity flags (set in upper bits of ecs_record_t::row) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsEntityIsId (1u << 31) +#define EcsEntityIsTarget (1u << 30) +#define EcsEntityIsTraversable (1u << 29) + + +//////////////////////////////////////////////////////////////////////////////// +//// Id flags (used by ecs_id_record_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsIdOnDeleteRemove (1u << 0) +#define EcsIdOnDeleteDelete (1u << 1) +#define EcsIdOnDeletePanic (1u << 2) +#define EcsIdOnDeleteMask\ + (EcsIdOnDeletePanic|EcsIdOnDeleteRemove|EcsIdOnDeleteDelete) + +#define EcsIdOnDeleteObjectRemove (1u << 3) +#define EcsIdOnDeleteObjectDelete (1u << 4) +#define EcsIdOnDeleteObjectPanic (1u << 5) +#define EcsIdOnDeleteObjectMask\ + (EcsIdOnDeleteObjectPanic|EcsIdOnDeleteObjectRemove|\ + EcsIdOnDeleteObjectDelete) + +#define EcsIdExclusive (1u << 6) +#define EcsIdDontInherit (1u << 7) +#define EcsIdTraversable (1u << 8) +#define EcsIdTag (1u << 9) +#define EcsIdWith (1u << 10) +#define EcsIdUnion (1u << 11) +#define EcsIdAlwaysOverride (1u << 12) + +#define EcsIdHasOnAdd (1u << 16) /* Same values as table flags */ +#define EcsIdHasOnRemove (1u << 17) +#define EcsIdHasOnSet (1u << 18) +#define EcsIdHasUnSet (1u << 19) +#define EcsIdHasOnTableFill (1u << 20) +#define EcsIdHasOnTableEmpty (1u << 21) +#define EcsIdHasOnTableCreate (1u << 22) +#define EcsIdHasOnTableDelete (1u << 23) +#define EcsIdEventMask\ + (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|EcsIdHasUnSet|\ + EcsIdHasOnTableFill|EcsIdHasOnTableEmpty|EcsIdHasOnTableCreate|\ + EcsIdHasOnTableDelete) + +#define EcsIdMarkedForDelete (1u << 30) + +/* Utilities for converting from flags to delete policies and vice versa */ +#define ECS_ID_ON_DELETE(flags) \ + ((ecs_entity_t[]){0, EcsRemove, EcsDelete, 0, EcsPanic}\ + [((flags) & EcsIdOnDeleteMask)]) +#define ECS_ID_ON_DELETE_TARGET(flags) ECS_ID_ON_DELETE(flags >> 3) +#define ECS_ID_ON_DELETE_FLAG(id) (1u << ((id) - EcsRemove)) +#define ECS_ID_ON_DELETE_TARGET_FLAG(id) (1u << (3 + ((id) - EcsRemove))) + + +//////////////////////////////////////////////////////////////////////////////// +//// Iterator flags (used by ecs_iter_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsIterIsValid (1u << 0u) /* Does iterator contain valid result */ +#define EcsIterNoData (1u << 1u) /* Does iterator provide (component) data */ +#define EcsIterIsInstanced (1u << 2u) /* Is iterator instanced */ +#define EcsIterHasShared (1u << 3u) /* Does result have shared terms */ +#define EcsIterTableOnly (1u << 4u) /* Result only populates table */ +#define EcsIterEntityOptional (1u << 5u) /* Treat terms with entity subject as optional */ +#define EcsIterNoResults (1u << 6u) /* Iterator has no results */ +#define EcsIterIgnoreThis (1u << 7u) /* Only evaluate non-this terms */ +#define EcsIterMatchVar (1u << 8u) +#define EcsIterHasCondSet (1u << 10u) /* Does iterator have conditionally set fields */ +#define EcsIterProfile (1u << 11u) /* Profile iterator performance */ +#define EcsIterTrivialSearch (1u << 12u) /* Trivial iterator mode */ +#define EcsIterTrivialSearchNoData (1u << 13u) /* Trivial iterator w/no data */ +#define EcsIterTrivialTest (1u << 14u) /* Trivial test mode (constrained $this) */ +#define EcsIterTrivialSearchWildcard (1u << 15u) /* Trivial search with wildcard ids */ + +//////////////////////////////////////////////////////////////////////////////// +//// Event flags (used by ecs_event_decs_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsEventTableOnly (1u << 4u) /* Table event (no data, same as iter flags) */ +#define EcsEventNoOnSet (1u << 16u) /* Don't emit OnSet/UnSet for inherited ids */ + +//////////////////////////////////////////////////////////////////////////////// +//// Filter flags (used by ecs_filter_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsFilterMatchThis (1u << 1u) /* Has terms that match This */ +#define EcsFilterMatchOnlyThis (1u << 2u) /* Has only terms that match This */ +#define EcsFilterMatchPrefab (1u << 3u) /* Does filter match prefabs */ +#define EcsFilterMatchDisabled (1u << 4u) /* Does filter match disabled entities */ +#define EcsFilterMatchEmptyTables (1u << 5u) /* Does filter return empty tables */ +#define EcsFilterMatchAnything (1u << 6u) /* False if filter has no/only Not terms */ +#define EcsFilterNoData (1u << 7u) /* When true, data fields won't be populated */ +#define EcsFilterIsInstanced (1u << 8u) /* Is filter instanced (see ecs_filter_desc_t) */ +#define EcsFilterPopulate (1u << 9u) /* Populate data, ignore non-matching fields */ +#define EcsFilterHasCondSet (1u << 10u) /* Does filter have conditionally set fields */ +#define EcsFilterUnresolvedByName (1u << 11u) /* Use by-name matching for unresolved entity identifiers */ +#define EcsFilterHasPred (1u << 12u) /* Filter has equality predicates */ +#define EcsFilterHasScopes (1u << 13u) /* Filter has query scopes */ +#define EcsFilterIsTrivial (1u << 14u) /* Trivial filter */ +#define EcsFilterMatchOnlySelf (1u << 15u) /* Filter has no up traversal */ +#define EcsFilterHasWildcards (1u << 16u) /* Filter has no up traversal */ +#define EcsFilterOwnsStorage (1u << 17u) /* Is ecs_filter_t object owned by filter */ +#define EcsFilterOwnsTermsStorage (1u << 18u) /* Is terms array owned by filter */ + +//////////////////////////////////////////////////////////////////////////////// +//// Table flags (used by ecs_table_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsTableHasBuiltins (1u << 1u) /* Does table have builtin components */ +#define EcsTableIsPrefab (1u << 2u) /* Does the table store prefabs */ +#define EcsTableHasIsA (1u << 3u) /* Does the table have IsA relationship */ +#define EcsTableHasChildOf (1u << 4u) /* Does the table type ChildOf relationship */ +#define EcsTableHasName (1u << 5u) /* Does the table type have (Identifier, Name) */ +#define EcsTableHasPairs (1u << 6u) /* Does the table type have pairs */ +#define EcsTableHasModule (1u << 7u) /* Does the table have module data */ +#define EcsTableIsDisabled (1u << 8u) /* Does the table type has EcsDisabled */ +#define EcsTableHasCtors (1u << 9u) +#define EcsTableHasDtors (1u << 10u) +#define EcsTableHasCopy (1u << 11u) +#define EcsTableHasMove (1u << 12u) +#define EcsTableHasUnion (1u << 13u) +#define EcsTableHasToggle (1u << 14u) +#define EcsTableHasOverrides (1u << 15u) + +#define EcsTableHasOnAdd (1u << 16u) /* Same values as id flags */ +#define EcsTableHasOnRemove (1u << 17u) +#define EcsTableHasOnSet (1u << 18u) +#define EcsTableHasUnSet (1u << 19u) +#define EcsTableHasOnTableFill (1u << 20u) +#define EcsTableHasOnTableEmpty (1u << 21u) +#define EcsTableHasOnTableCreate (1u << 22u) +#define EcsTableHasOnTableDelete (1u << 23u) + +#define EcsTableHasTraversable (1u << 25u) +#define EcsTableHasTarget (1u << 26u) + +#define EcsTableMarkedForDelete (1u << 30u) + +/* Composite table flags */ +#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) +#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasUnion | EcsTableHasToggle) +#define EcsTableHasAddActions (EcsTableHasIsA | EcsTableHasUnion | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet) +#define EcsTableHasRemoveActions (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet) + + +//////////////////////////////////////////////////////////////////////////////// +//// Query flags (used by ecs_query_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsQueryHasRefs (1u << 1u) /* Does query have references */ +#define EcsQueryIsSubquery (1u << 2u) /* Is query a subquery */ +#define EcsQueryIsOrphaned (1u << 3u) /* Is subquery orphaned */ +#define EcsQueryHasOutTerms (1u << 4u) /* Does query have out terms */ +#define EcsQueryHasNonThisOutTerms (1u << 5u) /* Does query have non-this out terms */ +#define EcsQueryHasMonitor (1u << 6u) /* Does query track changes */ +#define EcsQueryTrivialIter (1u << 7u) /* Does the query require special features to iterate */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Aperiodic action flags (used by ecs_run_aperiodic) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsAperiodicEmptyTables (1u << 1u) /* Process pending empty table events */ +#define EcsAperiodicComponentMonitors (1u << 2u) /* Process component monitors */ +#define EcsAperiodicEmptyQueries (1u << 4u) /* Process empty queries */ + +#ifdef __cplusplus +} +#endif + +#endif + + +#if defined(_WIN32) || defined(_MSC_VER) +#define ECS_TARGET_WINDOWS +#elif defined(__ANDROID__) +#define ECS_TARGET_ANDROID +#define ECS_TARGET_POSIX +#elif defined(__linux__) +#define ECS_TARGET_LINUX +#define ECS_TARGET_POSIX +#elif defined(__FreeBSD__) +#define ECS_TARGET_FREEBSD +#define ECS_TARGET_POSIX +#elif defined(__APPLE__) && defined(__MACH__) +#define ECS_TARGET_DARWIN +#define ECS_TARGET_POSIX +#elif defined(__EMSCRIPTEN__) +#define ECS_TARGET_EM +#define ECS_TARGET_POSIX +#endif + +#if defined(__MINGW32__) || defined(__MINGW64__) +#define ECS_TARGET_MINGW +#endif + +#if defined(_MSC_VER) +#ifndef __clang__ +#define ECS_TARGET_MSVC +#endif +#endif + +#if defined(__clang__) +#define ECS_TARGET_CLANG +#endif + +#if defined(__GNUC__) +#define ECS_TARGET_GNU +#endif + +/* Map between clang and apple clang versions, as version 13 has a difference in + * the format of __PRETTY_FUNCTION__ which enum reflection depends on. */ +#if defined(__clang__) + #if defined(__APPLE__) + #if __clang_major__ == 13 + #if __clang_minor__ < 1 + #define ECS_CLANG_VERSION 12 + #else + #define ECS_CLANG_VERSION 13 + #endif + #else + #define ECS_CLANG_VERSION __clang_major__ + #endif + #else + #define ECS_CLANG_VERSION __clang_major__ + #endif +#endif + +/* Ignored warnings */ +#if defined(ECS_TARGET_CLANG) +/* Ignore unknown options so we don't have to care about the compiler version */ +#pragma clang diagnostic ignored "-Wunknown-warning-option" +/* Warns for double or redundant semicolons. There are legitimate cases where a + * semicolon after an empty statement is useful, for example after a macro that + * is replaced with a code block. With this warning enabled, semicolons would + * only have to be added after macro's that are not code blocks, which in some + * cases isn't possible as the implementation of a macro can be different in + * debug/release mode. */ +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +/* This is valid in C99, and Flecs must be compiled as C99. */ +#pragma clang diagnostic ignored "-Wdeclaration-after-statement" +/* Clang attribute to detect fallthrough isn't supported on older versions. + * Implicit fallthrough is still detected by gcc and ignored with "fall through" + * comments */ +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +/* This warning prevents adding a default case when all enum constants are part + * of the switch. In C however an enum type can assume any value in the range of + * the type, and this warning makes it harder to catch invalid enum values. */ +#pragma clang diagnostic ignored "-Wcovered-switch-default" +/* This warning prevents some casts of function results to a different kind of + * type, e.g. casting an int result to double. Not very useful in practice, as + * it just forces the code to assign to a variable first, then cast. */ +#pragma clang diagnostic ignored "-Wbad-function-cast" +/* Format strings can be passed down from other functions. */ +#pragma clang diagnostic ignored "-Wformat-nonliteral" +/* Useful, but not reliable enough. It can incorrectly flag macro's as unused + * in standalone builds. */ +#pragma clang diagnostic ignored "-Wunused-macros" +#if __clang_major__ == 13 +/* clang 13 can throw this warning for a define in ctype.h */ +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif +/* Filenames aren't consistent across targets as they can use different casing + * (e.g. WinSock2 vs winsock2). */ +#pragma clang diagnostic ignored "-Wnonportable-system-include-path" +/* Enum reflection relies on testing constant values that may not be valid for + * the enumeration. */ +#pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +/* Very difficult to workaround this warning in C, especially for an ECS. */ +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +/* This warning gets thrown when trying to cast pointer returned from dlproc */ +#pragma clang diagnostic ignored "-Wcast-function-type-strict" +/* This warning can get thrown for expressions that evaluate to constants + * in debug/release mode. */ +#pragma clang diagnostic ignored "-Wconstant-logical-operand" +#elif defined(ECS_TARGET_GNU) +#ifndef __cplusplus +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" +#pragma GCC diagnostic ignored "-Wbad-function-cast" +#endif +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#pragma GCC diagnostic ignored "-Wunused-macros" +/* This warning gets thrown *sometimes* when not all members for a struct are + * provided in an initializer. Flecs heavily relies on descriptor structs that + * only require partly initialization, so this warning isn't useful. + * It doesn't introduce any safety issues (fields are guaranteed to be 0 + * initialized), and later versions of gcc (>=11) seem to no longer throw this + * warning. */ +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +/* Standard library dependencies */ +#include +#include +#include + +/* Non-standard but required. If not provided by platform, add manually. */ +#include + +/* Contains macros 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 defined(flecs_EXPORTS) && (defined(_MSC_VER) || defined(__MINGW32__)) + #define FLECS_API __declspec(dllexport) +#elif defined(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 + +/* Some symbols are only exported when building in debug build, to enable + * white-box testing of internal data structures */ +#ifndef FLECS_NDEBUG +#define FLECS_DBG_API FLECS_API +#else +#define FLECS_DBG_API +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Language support defines +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY +#include +#endif + +#ifndef NULL +#define NULL ((void*)0) +#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 + +/* Utility types to indicate usage as bitmask */ +typedef uint8_t ecs_flags8_t; +typedef uint16_t ecs_flags16_t; +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; + +/* Allocator type */ +typedef struct ecs_allocator_t ecs_allocator_t; + +#define ECS_SIZEOF(T) ECS_CAST(ecs_size_t, sizeof(T)) + +/* Use alignof in C++, or a trick in C. */ +#ifdef __cplusplus +#define ECS_ALIGNOF(T) static_cast(alignof(T)) +#elif defined(ECS_TARGET_MSVC) +#define ECS_ALIGNOF(T) (int64_t)__alignof(T) +#elif defined(ECS_TARGET_GNU) +#define ECS_ALIGNOF(T) (int64_t)__alignof__(T) +#else +#define ECS_ALIGNOF(T) ((int64_t)&((struct { char c; T d; } *)0)->d) +#endif + +#ifndef FLECS_NO_DEPRECATED_WARNINGS +#if defined(ECS_TARGET_GNU) +#define ECS_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(ECS_TARGET_MSVC) +#define ECS_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +#define ECS_DEPRECATED(msg) +#endif +#else +#define ECS_DEPRECATED(msg) +#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) +#define ECS_MIN(a, b) (((a) < (b)) ? a : b) + +/* Abstraction on top of C-style casts so that C functions can be used in C++ + * code without producing warnings */ +#ifndef __cplusplus +#define ECS_CAST(T, V) ((T)(V)) +#else +#define ECS_CAST(T, V) (static_cast(V)) +#endif + +/* Utility macro for doing const casts without warnings */ +#ifndef __cplusplus +#define ECS_CONST_CAST(type, value) ((type)(uintptr_t)(value)) +#else +#define ECS_CONST_CAST(type, value) (const_cast(value)) +#endif + +/* Utility macro for doing pointer casts without warnings */ +#ifndef __cplusplus +#define ECS_PTR_CAST(type, value) ((type)(uintptr_t)(value)) +#else +#define ECS_PTR_CAST(type, value) (reinterpret_cast(value)) +#endif + +/* Utility macro's to do bitwise comparisons between floats without warnings */ +#define ECS_EQ(a, b) (ecs_os_memcmp(&(a), &(b), sizeof(a)) == 0) +#define ECS_NEQ(a, b) (!ECS_EQ(a, b)) +#define ECS_EQZERO(a) ECS_EQ(a, (uint64_t){0}) +#define ECS_NEQZERO(a) ECS_NEQ(a, (uint64_t){0}) + +#define ECS_CONCAT(a, b) a ## b + +//////////////////////////////////////////////////////////////////////////////// +//// Magic numbers for sanity checking +//////////////////////////////////////////////////////////////////////////////// + +/* Magic number to identify the type of the object */ +#define ecs_world_t_magic (0x65637377) +#define ecs_stage_t_magic (0x65637373) +#define ecs_query_t_magic (0x65637371) +#define ecs_rule_t_magic (0x65637375) +#define ecs_table_t_magic (0x65637374) +#define ecs_filter_t_magic (0x65637366) +#define ecs_trigger_t_magic (0x65637372) +#define ecs_observer_t_magic (0x65637362) + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity id macros +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_ROW_MASK (0x0FFFFFFFu) +#define ECS_ROW_FLAGS_MASK (~ECS_ROW_MASK) +#define ECS_RECORD_TO_ROW(v) (ECS_CAST(int32_t, (ECS_CAST(uint32_t, v) & ECS_ROW_MASK))) +#define ECS_RECORD_TO_ROW_FLAGS(v) (ECS_CAST(uint32_t, v) & ECS_ROW_FLAGS_MASK) +#define ECS_ROW_TO_RECORD(row, flags) (ECS_CAST(uint32_t, (ECS_CAST(uint32_t, row) | (flags)))) + +#define ECS_ID_FLAGS_MASK (0xFFull << 60) +#define ECS_ENTITY_MASK (0xFFFFFFFFull) +#define ECS_GENERATION_MASK (0xFFFFull << 32) +#define ECS_GENERATION(e) ((e & ECS_GENERATION_MASK) >> 32) +#define ECS_GENERATION_INC(e) ((e & ~ECS_GENERATION_MASK) | ((0xFFFF & (ECS_GENERATION(e) + 1)) << 32)) +#define ECS_COMPONENT_MASK (~ECS_ID_FLAGS_MASK) +#define ECS_HAS_ID_FLAG(e, flag) ((e) & ECS_##flag) +#define ECS_IS_PAIR(id) (((id) & ECS_ID_FLAGS_MASK) == ECS_PAIR) +#define ECS_PAIR_FIRST(e) (ecs_entity_t_hi(e & ECS_COMPONENT_MASK)) +#define ECS_PAIR_SECOND(e) (ecs_entity_t_lo(e)) +#define ECS_HAS_RELATION(e, rel) (ECS_HAS_ID_FLAG(e, PAIR) && (ECS_PAIR_FIRST(e) == rel)) + + +//////////////////////////////////////////////////////////////////////////////// +//// Convert between C typenames and variables +//////////////////////////////////////////////////////////////////////////////// + +/** Translate C type to id. */ +#define ecs_id(T) FLECS_ID##T##ID_ + + +//////////////////////////////////////////////////////////////////////////////// +//// Utilities for working with pair identifiers +//////////////////////////////////////////////////////////////////////////////// + +#define ecs_entity_t_lo(value) ECS_CAST(uint32_t, value) +#define ecs_entity_t_hi(value) ECS_CAST(uint32_t, (value) >> 32) +#define ecs_entity_t_comb(lo, hi) ((ECS_CAST(uint64_t, hi) << 32) + ECS_CAST(uint32_t, lo)) + +#define ecs_pair(pred, obj) (ECS_PAIR | ecs_entity_t_comb(obj, pred)) +#define ecs_pair_t(pred, obj) (ECS_PAIR | ecs_entity_t_comb(obj, ecs_id(pred))) +#define ecs_pair_first(world, pair) ecs_get_alive(world, ECS_PAIR_FIRST(pair)) +#define ecs_pair_second(world, pair) ecs_get_alive(world, ECS_PAIR_SECOND(pair)) +#define ecs_pair_relation ecs_pair_first +#define ecs_pair_object ecs_pair_second + +#define ecs_poly_id(tag) ecs_pair(ecs_id(EcsPoly), tag) + + +//////////////////////////////////////////////////////////////////////////////// +//// Debug macros +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_NDEBUG +#define ECS_TABLE_LOCK(world, table) ecs_table_lock(world, table) +#define ECS_TABLE_UNLOCK(world, table) ecs_table_unlock(world, table) +#else +#define ECS_TABLE_LOCK(world, table) +#define ECS_TABLE_UNLOCK(world, table) +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Actions that drive iteration +//////////////////////////////////////////////////////////////////////////////// + +#define EcsIterNextYield (0) /* Move to next table, yield current */ +#define EcsIterYield (-1) /* Stay on current table, yield */ +#define EcsIterNext (1) /* Move to next table, don't yield */ + +//////////////////////////////////////////////////////////////////////////////// +//// Convenience macros for ctor, dtor, move and copy +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY + +/* Constructor/Destructor convenience macro */ +#define ECS_XTOR_IMPL(type, postfix, var, ...)\ + void type##_##postfix(\ + void *_ptr,\ + int32_t _count,\ + const ecs_type_info_t *type_info)\ + {\ + (void)_ptr;\ + (void)_count;\ + (void)type_info;\ + for (int32_t i = 0; i < _count; i ++) {\ + type *var = &((type*)_ptr)[i];\ + (void)var;\ + __VA_ARGS__\ + }\ + } + +/* Copy convenience macro */ +#define ECS_COPY_IMPL(type, dst_var, src_var, ...)\ + void type##_##copy(\ + void *_dst_ptr,\ + const void *_src_ptr,\ + int32_t _count,\ + const ecs_type_info_t *type_info)\ + {\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_count;\ + (void)type_info;\ + for (int32_t i = 0; i < _count; i ++) {\ + type *dst_var = &((type*)_dst_ptr)[i];\ + const type *src_var = &((const type*)_src_ptr)[i];\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } + +/* Move convenience macro */ +#define ECS_MOVE_IMPL(type, dst_var, src_var, ...)\ + void type##_##move(\ + void *_dst_ptr,\ + void *_src_ptr,\ + int32_t _count,\ + const ecs_type_info_t *type_info)\ + {\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_count;\ + (void)type_info;\ + for (int32_t i = 0; i < _count; i ++) {\ + type *dst_var = &((type*)_dst_ptr)[i];\ + type *src_var = &((type*)_src_ptr)[i];\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } + +#define ECS_HOOK_IMPL(type, func, var, ...)\ + void func(ecs_iter_t *_it)\ + {\ + for (int32_t i = 0; i < _it->count; i ++) {\ + ecs_entity_t entity = _it->entities[i];\ + type *var = &((type*)_it->ptrs[0])[i];\ + (void)entity;\ + (void)var;\ + __VA_ARGS__\ + }\ + } + +#endif + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file vec.h + * @brief Vector with allocator support. + */ + +#ifndef FLECS_VEC_H +#define FLECS_VEC_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/** A component column. */ +typedef struct ecs_vec_t { + void *array; + int32_t count; + int32_t size; +#ifdef FLECS_SANITIZE + ecs_size_t elem_size; +#endif +} ecs_vec_t; + +FLECS_API +ecs_vec_t* ecs_vec_init( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); + +#define ecs_vec_init_t(allocator, vec, T, elem_count) \ + ecs_vec_init(allocator, vec, ECS_SIZEOF(T), elem_count) + +FLECS_API +void ecs_vec_init_if( + ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_init_if_t(vec, T) \ + ecs_vec_init_if(vec, ECS_SIZEOF(T)) + +FLECS_API +void ecs_vec_fini( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_fini_t(allocator, vec, T) \ + ecs_vec_fini(allocator, vec, ECS_SIZEOF(T)) + +FLECS_API +ecs_vec_t* ecs_vec_reset( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_reset_t(allocator, vec, T) \ + ecs_vec_reset(allocator, vec, ECS_SIZEOF(T)) + +FLECS_API +void ecs_vec_clear( + ecs_vec_t *vec); + +FLECS_API +void* ecs_vec_append( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_append_t(allocator, vec, T) \ + ECS_CAST(T*, ecs_vec_append(allocator, vec, ECS_SIZEOF(T))) + +FLECS_API +void ecs_vec_remove( + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem); + +#define ecs_vec_remove_t(vec, T, elem) \ + ecs_vec_remove(vec, ECS_SIZEOF(T), elem) + +FLECS_API +void ecs_vec_remove_last( + ecs_vec_t *vec); + +FLECS_API +ecs_vec_t ecs_vec_copy( + struct ecs_allocator_t *allocator, + const ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_copy_t(allocator, vec, T) \ + ecs_vec_copy(allocator, vec, ECS_SIZEOF(T)) + +FLECS_API +ecs_vec_t ecs_vec_copy_shrink( + struct ecs_allocator_t *allocator, + const ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_copy_shrink_t(allocator, vec, T) \ + ecs_vec_copy_shrink(allocator, vec, ECS_SIZEOF(T)) + +FLECS_API +void ecs_vec_reclaim( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_reclaim_t(allocator, vec, T) \ + ecs_vec_reclaim(allocator, vec, ECS_SIZEOF(T)) + +FLECS_API +void ecs_vec_set_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); + +#define ecs_vec_set_size_t(allocator, vec, T, elem_count) \ + ecs_vec_set_size(allocator, vec, ECS_SIZEOF(T), elem_count) + +FLECS_API +void ecs_vec_set_min_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); + +#define ecs_vec_set_min_size_t(allocator, vec, T, elem_count) \ + ecs_vec_set_min_size(allocator, vec, ECS_SIZEOF(T), elem_count) + +FLECS_API +void ecs_vec_set_min_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); + +#define ecs_vec_set_min_count_t(allocator, vec, T, elem_count) \ + ecs_vec_set_min_count(allocator, vec, ECS_SIZEOF(T), elem_count) + +FLECS_API +void ecs_vec_set_min_count_zeromem( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); + +#define ecs_vec_set_min_count_zeromem_t(allocator, vec, T, elem_count) \ + ecs_vec_set_min_count_zeromem(allocator, vec, ECS_SIZEOF(T), elem_count) + +FLECS_API +void ecs_vec_set_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); + +#define ecs_vec_set_count_t(allocator, vec, T, elem_count) \ + ecs_vec_set_count(allocator, vec, ECS_SIZEOF(T), elem_count) + +FLECS_API +void* ecs_vec_grow( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); + +#define ecs_vec_grow_t(allocator, vec, T, elem_count) \ + ecs_vec_grow(allocator, vec, ECS_SIZEOF(T), elem_count) + +FLECS_API +int32_t ecs_vec_count( + const ecs_vec_t *vec); + +FLECS_API +int32_t ecs_vec_size( + const ecs_vec_t *vec); + +FLECS_API +void* ecs_vec_get( + const ecs_vec_t *vec, + ecs_size_t size, + int32_t index); + +#define ecs_vec_get_t(vec, T, index) \ + ECS_CAST(T*, ecs_vec_get(vec, ECS_SIZEOF(T), index)) + +FLECS_API +void* ecs_vec_first( + const ecs_vec_t *vec); + +#define ecs_vec_first_t(vec, T) \ + ECS_CAST(T*, ecs_vec_first(vec)) + +FLECS_API +void* ecs_vec_last( + const ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_last_t(vec, T) \ + ECS_CAST(T*, ecs_vec_last(vec, ECS_SIZEOF(T))) + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file sparse.h + * @brief Sparse set data structure. + */ + +#ifndef FLECS_SPARSE_H +#define FLECS_SPARSE_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/** The number of elements in a single page */ +#define FLECS_SPARSE_PAGE_SIZE (1 << FLECS_SPARSE_PAGE_BITS) + +typedef struct ecs_sparse_t { + ecs_vec_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_vec_t pages; /* Chunks with sparse arrays & data */ + ecs_size_t size; /* Element size */ + int32_t count; /* Number of alive entries */ + uint64_t max_id; /* Local max index (if no global is set) */ + struct ecs_allocator_t *allocator; + struct ecs_block_allocator_t *page_allocator; +} ecs_sparse_t; + +/** Initialize sparse set */ +FLECS_DBG_API +void flecs_sparse_init( + ecs_sparse_t *result, + struct ecs_allocator_t *allocator, + struct ecs_block_allocator_t *page_allocator, + ecs_size_t size); + +#define flecs_sparse_init_t(result, allocator, page_allocator, T)\ + flecs_sparse_init(result, allocator, page_allocator, ECS_SIZEOF(T)) + +FLECS_DBG_API +void flecs_sparse_fini( + ecs_sparse_t *sparse); + +/** Remove all elements from sparse set */ +FLECS_DBG_API +void flecs_sparse_clear( + ecs_sparse_t *sparse); + +/** Add element to sparse set, this generates or recycles an id */ +FLECS_DBG_API +void* flecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define flecs_sparse_add_t(sparse, T)\ + ECS_CAST(T*, flecs_sparse_add(sparse, ECS_SIZEOF(T))) + +/** Get last issued id. */ +FLECS_DBG_API +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse); + +/** Generate or recycle a new id. */ +FLECS_DBG_API +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse); + +/** Remove an element */ +FLECS_DBG_API +void flecs_sparse_remove( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_remove_t(sparse, T, id)\ + flecs_sparse_remove(sparse, ECS_SIZEOF(T), id) + +/** Test if id is alive, which requires the generation count to match. */ +FLECS_DBG_API +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Get value from sparse set by dense id. This function is useful in + * combination with flecs_sparse_count for iterating all values in the set. */ +FLECS_DBG_API +void* flecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index); + +#define flecs_sparse_get_dense_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) + +/** Get the number of alive elements in the sparse set. */ +FLECS_DBG_API +int32_t flecs_sparse_count( + 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_DBG_API +void* flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_get_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_get(sparse, ECS_SIZEOF(T), index)) + +/** Same as flecs_sparse_get, but doesn't assert if id is not alive. */ +FLECS_DBG_API +void* flecs_sparse_try( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_try_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_try(sparse, ECS_SIZEOF(T), index)) + +/** Like get_sparse, but don't care whether element is alive or not. */ +FLECS_DBG_API +void* flecs_sparse_get_any( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_get_any_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_get_any(sparse, ECS_SIZEOF(T), index)) + +/** Get or create element by (sparse) id. */ +FLECS_DBG_API +void* flecs_sparse_ensure( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_ensure_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_ensure(sparse, ECS_SIZEOF(T), index)) + +/** Fast version of ensure, no liveliness checking */ +FLECS_DBG_API +void* flecs_sparse_ensure_fast( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_ensure_fast_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_ensure_fast(sparse, ECS_SIZEOF(T), index)) + +/** Get pointer to ids (alive and not alive). Use with count() or size(). */ +FLECS_DBG_API +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse); + +/* Publicly exposed APIs + * The flecs_ functions aren't exposed directly as this can cause some + * optimizers to not consider them for link time optimization. */ + +FLECS_API +void ecs_sparse_init( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_init_t(sparse, T)\ + ecs_sparse_init(sparse, ECS_SIZEOF(T)) + +FLECS_API +void* ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_add_t(sparse, T)\ + ECS_CAST(T*, ecs_sparse_add(sparse, ECS_SIZEOF(T))) + +FLECS_API +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse); + +FLECS_API +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse); + +/** Override the generation count for a specific id */ +FLECS_API +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t id); + +FLECS_API +void* ecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index); + +#define ecs_sparse_get_dense_t(sparse, T, index)\ + ECS_CAST(T*, ecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) + +FLECS_API +void* ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define ecs_sparse_get_t(sparse, T, index)\ + ECS_CAST(T*, ecs_sparse_get(sparse, ECS_SIZEOF(T), index)) + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file block_allocator.h + * @brief Block allocator. + */ + +#ifndef FLECS_BLOCK_ALLOCATOR_H +#define FLECS_BLOCK_ALLOCATOR_H + + +typedef struct ecs_block_allocator_block_t { + void *memory; + struct ecs_block_allocator_block_t *next; +} ecs_block_allocator_block_t; + +typedef struct ecs_block_allocator_chunk_header_t { + struct ecs_block_allocator_chunk_header_t *next; +} ecs_block_allocator_chunk_header_t; + +typedef struct ecs_block_allocator_t { + ecs_block_allocator_chunk_header_t *head; + ecs_block_allocator_block_t *block_head; + ecs_block_allocator_block_t *block_tail; + int32_t chunk_size; + int32_t data_size; + int32_t chunks_per_block; + int32_t block_size; + int32_t alloc_count; +} ecs_block_allocator_t; + +FLECS_API +void flecs_ballocator_init( + ecs_block_allocator_t *ba, + ecs_size_t size); + +#define flecs_ballocator_init_t(ba, T)\ + flecs_ballocator_init(ba, ECS_SIZEOF(T)) +#define flecs_ballocator_init_n(ba, T, count)\ + flecs_ballocator_init(ba, ECS_SIZEOF(T) * count) + +FLECS_API +ecs_block_allocator_t* flecs_ballocator_new( + ecs_size_t size); + +#define flecs_ballocator_new_t(T)\ + flecs_ballocator_new(ECS_SIZEOF(T)) +#define flecs_ballocator_new_n(T, count)\ + flecs_ballocator_new(ECS_SIZEOF(T) * count) + +FLECS_API +void flecs_ballocator_fini( + ecs_block_allocator_t *ba); + +FLECS_API +void flecs_ballocator_free( + ecs_block_allocator_t *ba); + +FLECS_API +void* flecs_balloc( + ecs_block_allocator_t *allocator); + +FLECS_API +void* flecs_bcalloc( + ecs_block_allocator_t *allocator); + +FLECS_API +void flecs_bfree( + ecs_block_allocator_t *allocator, + void *memory); + +FLECS_API +void* flecs_brealloc( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory); + +FLECS_API +void* flecs_bdup( + ecs_block_allocator_t *ba, + void *memory); + +#endif + +/** + * @file map.h + * @brief Map data structure. + */ + +#ifndef FLECS_MAP_H +#define FLECS_MAP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint64_t ecs_map_data_t; +typedef ecs_map_data_t ecs_map_key_t; +typedef ecs_map_data_t ecs_map_val_t; + +/* Map type */ +typedef struct ecs_bucket_entry_t { + ecs_map_key_t key; + ecs_map_val_t value; + struct ecs_bucket_entry_t *next; +} ecs_bucket_entry_t; + +typedef struct ecs_bucket_t { + ecs_bucket_entry_t *first; +} ecs_bucket_t; + +typedef struct ecs_map_t { + uint8_t bucket_shift; + bool shared_allocator; + ecs_bucket_t *buckets; + int32_t bucket_count; + int32_t count; + struct ecs_block_allocator_t *entry_allocator; + struct ecs_allocator_t *allocator; +} ecs_map_t; + +typedef struct ecs_map_iter_t { + const ecs_map_t *map; + ecs_bucket_t *bucket; + ecs_bucket_entry_t *entry; + ecs_map_data_t *res; +} ecs_map_iter_t; + +typedef struct ecs_map_params_t { + struct ecs_allocator_t *allocator; + struct ecs_block_allocator_t entry_allocator; +} ecs_map_params_t; + +/* Function/macro postfixes meaning: + * _ptr: access ecs_map_val_t as void* + * _ref: access ecs_map_val_t* as T** + * _deref: dereferences a _ref + * _alloc: if _ptr is NULL, alloc + * _free: if _ptr is not NULL, free + */ + +FLECS_API +void ecs_map_params_init( + ecs_map_params_t *params, + struct ecs_allocator_t *allocator); + +FLECS_API +void ecs_map_params_fini( + ecs_map_params_t *params); + +/** Initialize new map. */ +FLECS_API +void ecs_map_init( + ecs_map_t *map, + struct ecs_allocator_t *allocator); + +/** Initialize new map. */ +FLECS_API +void ecs_map_init_w_params( + ecs_map_t *map, + ecs_map_params_t *params); + +/** Initialize new map if uninitialized, leave as is otherwise */ +FLECS_API +void ecs_map_init_if( + ecs_map_t *map, + struct ecs_allocator_t *allocator); + +FLECS_API +void ecs_map_init_w_params_if( + ecs_map_t *result, + ecs_map_params_t *params); + +/** Deinitialize map. */ +FLECS_API +void ecs_map_fini( + ecs_map_t *map); + +/** Get element for key, returns NULL if they key doesn't exist. */ +FLECS_API +ecs_map_val_t* ecs_map_get( + const ecs_map_t *map, + ecs_map_key_t key); + +/* Get element as pointer (auto-dereferences _ptr) */ +FLECS_API +void* ecs_map_get_deref_( + const ecs_map_t *map, + ecs_map_key_t key); + +/** Get or insert element for key. */ +FLECS_API +ecs_map_val_t* ecs_map_ensure( + ecs_map_t *map, + ecs_map_key_t key); + +/** Get or insert pointer element for key, allocate if the pointer is NULL */ +FLECS_API +void* ecs_map_ensure_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +/** Insert element for key. */ +FLECS_API +void ecs_map_insert( + ecs_map_t *map, + ecs_map_key_t key, + ecs_map_val_t value); + +/** Insert pointer element for key, populate with new allocation. */ +FLECS_API +void* ecs_map_insert_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +/** Remove key from map. */ +FLECS_API +ecs_map_val_t ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key); + +/* Remove pointer element, free if not NULL */ +FLECS_API +void ecs_map_remove_free( + 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. */ +#define ecs_map_count(map) ((map) ? (map)->count : 0) + +/** Is map initialized */ +#define ecs_map_is_init(map) ((map) ? (map)->bucket_shift != 0 : false) + +/** 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 +bool ecs_map_next( + ecs_map_iter_t *iter); + +/** Copy map. */ +FLECS_API +void ecs_map_copy( + ecs_map_t *dst, + const ecs_map_t *src); + +#define ecs_map_get_ref(m, T, k) ECS_CAST(T**, ecs_map_get(m, k)) +#define ecs_map_get_deref(m, T, k) ECS_CAST(T*, ecs_map_get_deref_(m, k)) +#define ecs_map_ensure_ref(m, T, k) ECS_CAST(T**, ecs_map_ensure(m, k)) + +#define ecs_map_insert_ptr(m, k, v) ecs_map_insert(m, k, ECS_CAST(ecs_map_val_t, ECS_PTR_CAST(uintptr_t, v))) +#define ecs_map_insert_alloc_t(m, T, k) ECS_CAST(T*, ecs_map_insert_alloc(m, ECS_SIZEOF(T), k)) +#define ecs_map_ensure_alloc_t(m, T, k) ECS_PTR_CAST(T*, (uintptr_t)ecs_map_ensure_alloc(m, ECS_SIZEOF(T), k)) +#define ecs_map_remove_ptr(m, k) (ECS_PTR_CAST(void*, ECS_CAST(uintptr_t, (ecs_map_remove(m, k))))) + +#define ecs_map_key(it) ((it)->res[0]) +#define ecs_map_value(it) ((it)->res[1]) +#define ecs_map_ptr(it) ECS_PTR_CAST(void*, ECS_CAST(uintptr_t, ecs_map_value(it))) +#define ecs_map_ref(it, T) (ECS_CAST(T**, &((it)->res[1]))) + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file allocator.h + * @brief Allocator that returns memory objects of any size. + */ + +#ifndef FLECS_ALLOCATOR_H +#define FLECS_ALLOCATOR_H + + +FLECS_DBG_API extern int64_t ecs_block_allocator_alloc_count; +FLECS_DBG_API extern int64_t ecs_block_allocator_free_count; +FLECS_DBG_API extern int64_t ecs_stack_allocator_alloc_count; +FLECS_DBG_API extern int64_t ecs_stack_allocator_free_count; + +struct ecs_allocator_t { + ecs_block_allocator_t chunks; + struct ecs_sparse_t sizes; /* */ +}; + +FLECS_API +void flecs_allocator_init( + ecs_allocator_t *a); + +FLECS_API +void flecs_allocator_fini( + ecs_allocator_t *a); + +FLECS_API +ecs_block_allocator_t* flecs_allocator_get( + ecs_allocator_t *a, + ecs_size_t size); + +FLECS_API +char* flecs_strdup( + ecs_allocator_t *a, + const char* str); + +FLECS_API +void flecs_strfree( + ecs_allocator_t *a, + char* str); + +FLECS_API +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src); + +#define flecs_allocator(obj) (&obj->allocators.dyn) + +#define flecs_alloc(a, size) flecs_balloc(flecs_allocator_get(a, size)) +#define flecs_alloc_t(a, T) flecs_alloc(a, ECS_SIZEOF(T)) +#define flecs_alloc_n(a, T, count) flecs_alloc(a, ECS_SIZEOF(T) * (count)) + +#define flecs_calloc(a, size) flecs_bcalloc(flecs_allocator_get(a, size)) +#define flecs_calloc_t(a, T) flecs_calloc(a, ECS_SIZEOF(T)) +#define flecs_calloc_n(a, T, count) flecs_calloc(a, ECS_SIZEOF(T) * (count)) + +#define flecs_free(a, size, ptr) flecs_bfree(flecs_allocator_get(a, size), ptr) +#define flecs_free_t(a, T, ptr) flecs_free(a, ECS_SIZEOF(T), ptr) +#define flecs_free_n(a, T, count, ptr) flecs_free(a, ECS_SIZEOF(T) * (count), ptr) + +#define flecs_realloc(a, size_dst, size_src, ptr)\ + flecs_brealloc(flecs_allocator_get(a, size_dst),\ + flecs_allocator_get(a, size_src),\ + ptr) +#define flecs_realloc_n(a, T, count_dst, count_src, ptr)\ + flecs_realloc(a, ECS_SIZEOF(T) * (count_dst), ECS_SIZEOF(T) * (count_src), ptr) + +#define flecs_dup_n(a, T, count, ptr) flecs_dup(a, ECS_SIZEOF(T) * (count), ptr) + +#endif + +/** + * @file strbuf.h + * @brief Utility for constructing strings. + */ + +#ifndef FLECS_STRBUF_H_ +#define FLECS_STRBUF_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +/* Fixes missing field initializer warning on g++ */ +#define ECS_STRBUF_INIT (ecs_strbuf_t){} +#else +#define ECS_STRBUF_INIT (ecs_strbuf_t){0} +#endif + +#define ECS_STRBUF_SMALL_STRING_SIZE (512) +#define ECS_STRBUF_MAX_LIST_DEPTH (32) + +typedef struct ecs_strbuf_list_elem { + int32_t count; + const char *separator; +} ecs_strbuf_list_elem; + +typedef struct ecs_strbuf_t { + char *content; + ecs_size_t length; + ecs_size_t size; + + ecs_strbuf_list_elem list_stack[ECS_STRBUF_MAX_LIST_DEPTH]; + int32_t list_sp; + + char small_string[ECS_STRBUF_SMALL_STRING_SIZE]; +} ecs_strbuf_t; + +/* Append format string to a buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +void 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 +void 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 +void ecs_strbuf_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +/* Append character to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +void ecs_strbuf_appendch( + ecs_strbuf_t *buffer, + char ch); + +/* Append int to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +void ecs_strbuf_appendint( + ecs_strbuf_t *buffer, + int64_t v); + +/* Append float to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +void ecs_strbuf_appendflt( + ecs_strbuf_t *buffer, + double v, + char nan_delim); + +/* Append boolean to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +void ecs_strbuf_appendbool( + ecs_strbuf_t *buffer, + bool v); + +/* Append source buffer to destination buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +void ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer); + +/* Append n characters to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +void ecs_strbuf_appendstrn( + ecs_strbuf_t *buffer, + const char *str, + int32_t n); + +/* Return result string */ +FLECS_API +char* ecs_strbuf_get( + ecs_strbuf_t *buffer); + +/* Return small string from first element (appends \0) */ +FLECS_API +char* ecs_strbuf_get_small( + 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 character to as new element in list. */ +FLECS_API +void ecs_strbuf_list_appendch( + ecs_strbuf_t *buffer, + char ch); + +/* Append formatted string as a new element in list */ +FLECS_API +void ecs_strbuf_list_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...); + +/* Append string as a new element in list */ +FLECS_API +void ecs_strbuf_list_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +/* Append string as a new element in list */ +FLECS_API +void ecs_strbuf_list_appendstrn( + ecs_strbuf_t *buffer, + const char *str, + int32_t n); + +FLECS_API +int32_t ecs_strbuf_written( + const ecs_strbuf_t *buffer); + +#define ecs_strbuf_appendlit(buf, str)\ + ecs_strbuf_appendstrn(buf, str, (int32_t)(sizeof(str) - 1)) + +#define ecs_strbuf_list_appendlit(buf, str)\ + ecs_strbuf_list_appendstrn(buf, str, (int32_t)(sizeof(str) - 1)) + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file os_api.h + * @brief Operating system abstraction API. + * + * This file contains the operating system abstraction API. The flecs core + * library avoids OS/runtime specific API calls as much as possible. Instead it + * provides an interface that can be implemented by applications. + * + * Examples for how to implement this interface can be found in the + * examples/os_api folder. + */ + +#ifndef FLECS_OS_API_H +#define FLECS_OS_API_H + +/** + * @defgroup c_os_api OS API + * @ingroup c + * Interface for providing OS specific functionality. + * + * @{ + */ + +#include +#include +#include + +#if defined(ECS_TARGET_WINDOWS) +#include +#elif defined(ECS_TARGET_FREEBSD) +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_time_t { + uint32_t sec; + uint32_t nanosec; +} ecs_time_t; + +/* Allocation counters */ +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; +typedef uintptr_t ecs_os_sock_t; + +/* 64 bit thread id */ +typedef uint64_t ecs_os_thread_id_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); + +typedef +ecs_os_thread_id_t (*ecs_os_api_thread_self_t)(void); + +/* Tasks */ +typedef +ecs_os_thread_t (*ecs_os_api_task_new_t)( + ecs_os_thread_callback_t callback, + void *param); + +typedef +void* (*ecs_os_api_task_join_t)( + ecs_os_thread_t thread); + +/* Atomic increment / decrement */ +typedef +int32_t (*ecs_os_api_ainc_t)( + int32_t *value); + +typedef +int64_t (*ecs_os_api_lainc_t)( + int64_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_enable_high_timer_resolution_t)( + bool enable); + +typedef +void (*ecs_os_api_get_time_t)( + ecs_time_t *time_out); + +typedef +uint64_t (*ecs_os_api_now_t)(void); + +/* Logging */ +typedef +void (*ecs_os_api_log_t)( + int32_t level, /* Logging level */ + const char *file, /* File where message was logged */ + int32_t line, /* Line it was logged */ + const char *msg); + +/* 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 + * macros 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_; + ecs_os_api_thread_self_t thread_self_; + + /* Tasks */ + ecs_os_api_thread_new_t task_new_; + ecs_os_api_thread_join_t task_join_; + + /* Atomic increment / decrement */ + ecs_os_api_ainc_t ainc_; + ecs_os_api_ainc_t adec_; + ecs_os_api_lainc_t lainc_; + ecs_os_api_lainc_t ladec_; + + /* 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_now_t now_; + ecs_os_api_get_time_t get_time_; + + /* Logging */ + ecs_os_api_log_t log_; /* Logging function. The level should be interpreted as: */ + /* >0: Debug tracing. Only enabled in debug builds. */ + /* 0: Tracing. Enabled in debug/release builds. */ + /* -2: Warning. An issue occurred, but operation was successful. */ + /* -3: Error. An issue occurred, and operation was unsuccessful. */ + /* -4: Fatal. An issue occurred, and application must quit. */ + + /* 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_; + + /* Trace level */ + int32_t log_level_; + + /* Trace indentation */ + int32_t log_indent_; + + /* Last error code */ + int32_t log_last_error_; + + /* Last recorded timestamp */ + int64_t log_last_timestamp_; + + /* OS API flags */ + ecs_flags32_t flags_; + + /* File used for logging output (hint, log_ decides where to write) */ + FILE *log_out_; +} 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 +ecs_os_api_t ecs_os_get_api(void); + +FLECS_API +void ecs_os_set_api_defaults(void); + +/* Memory management */ +#ifndef ecs_os_malloc +#define ecs_os_malloc(size) ecs_os_api.malloc_(size) +#endif +#ifndef ecs_os_free +#define ecs_os_free(ptr) ecs_os_api.free_(ptr) +#endif +#ifndef ecs_os_realloc +#define ecs_os_realloc(ptr, size) ecs_os_api.realloc_(ptr, size) +#endif +#ifndef ecs_os_calloc +#define ecs_os_calloc(size) ecs_os_api.calloc_(size) +#endif +#if defined(ECS_TARGET_WINDOWS) +#define ecs_os_alloca(size) _alloca((size_t)(size)) +#else +#define ecs_os_alloca(size) alloca((size_t)(size)) +#endif + +#define ecs_os_malloc_t(T) ECS_CAST(T*, ecs_os_malloc(ECS_SIZEOF(T))) +#define ecs_os_malloc_n(T, count) ECS_CAST(T*, ecs_os_malloc(ECS_SIZEOF(T) * (count))) +#define ecs_os_calloc_t(T) ECS_CAST(T*, ecs_os_calloc(ECS_SIZEOF(T))) +#define ecs_os_calloc_n(T, count) ECS_CAST(T*, ecs_os_calloc(ECS_SIZEOF(T) * (count))) + +#define ecs_os_realloc_t(ptr, T) ECS_CAST(T*, ecs_os_realloc(ptr, ECS_SIZEOF(T))) +#define ecs_os_realloc_n(ptr, T, count) ECS_CAST(T*, ecs_os_realloc(ptr, ECS_SIZEOF(T) * (count))) +#define ecs_os_alloca_t(T) ECS_CAST(T*, ecs_os_alloca(ECS_SIZEOF(T))) +#define ecs_os_alloca_n(T, count) ECS_CAST(T*, ecs_os_alloca(ECS_SIZEOF(T) * (count))) + +/* Strings */ +#ifndef ecs_os_strdup +#define ecs_os_strdup(str) ecs_os_api.strdup_(str) +#endif + +#ifdef __cplusplus +#define ecs_os_strlen(str) static_cast(strlen(str)) +#define ecs_os_strncmp(str1, str2, num) strncmp(str1, str2, static_cast(num)) +#define ecs_os_memcmp(ptr1, ptr2, num) memcmp(ptr1, ptr2, static_cast(num)) +#define ecs_os_memcpy(ptr1, ptr2, num) memcpy(ptr1, ptr2, static_cast(num)) +#define ecs_os_memset(ptr, value, num) memset(ptr, value, static_cast(num)) +#define ecs_os_memmove(dst, src, size) memmove(dst, src, static_cast(size)) +#else +#define ecs_os_strlen(str) (ecs_size_t)strlen(str) +#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(dst, src, size) memmove(dst, src, (size_t)(size)) +#endif + +#define ecs_os_memcpy_t(ptr1, ptr2, T) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T)) +#define ecs_os_memcpy_n(ptr1, ptr2, T, count) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T) * count) +#define ecs_os_memcmp_t(ptr1, ptr2, T) ecs_os_memcmp(ptr1, ptr2, ECS_SIZEOF(T)) + +#define ecs_os_memmove_t(ptr1, ptr2, T) ecs_os_memmove(ptr1, ptr2, ECS_SIZEOF(T)) +#define ecs_os_memmove_n(ptr1, ptr2, T, count) ecs_os_memmove(ptr1, ptr2, ECS_SIZEOF(T) * count) +#define ecs_os_memmove_t(ptr1, ptr2, T) ecs_os_memmove(ptr1, ptr2, ECS_SIZEOF(T)) + +#define ecs_os_strcmp(str1, str2) strcmp(str1, str2) +#define ecs_os_memset_t(ptr, value, T) ecs_os_memset(ptr, value, ECS_SIZEOF(T)) +#define ecs_os_memset_n(ptr, value, T, count) ecs_os_memset(ptr, value, ECS_SIZEOF(T) * count) +#define ecs_os_zeromem(ptr) ecs_os_memset(ptr, 0, ECS_SIZEOF(*ptr)) + +#define ecs_os_memdup_t(ptr, T) ecs_os_memdup(ptr, ECS_SIZEOF(T)) +#define ecs_os_memdup_n(ptr, T, count) ecs_os_memdup(ptr, ECS_SIZEOF(T) * count) + +#define ecs_offset(ptr, T, index)\ + ECS_CAST(T*, ECS_OFFSET(ptr, ECS_SIZEOF(T) * index)) + +#if !defined(ECS_TARGET_POSIX) && !defined(ECS_TARGET_MINGW) +#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) +#ifdef __cplusplus +#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, static_cast(num)) +#else +#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, (size_t)(num)) +#endif +#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) +#ifdef __cplusplus +#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, static_cast(num)) +#else +#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, (size_t)(num)) +#endif +#endif + +/* Files */ +#ifndef ECS_TARGET_POSIX +#define ecs_os_fopen(result, file, mode) fopen_s(result, file, mode) +#else +#define ecs_os_fopen(result, file, mode) (*(result)) = fopen(file, mode) +#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) +#define ecs_os_thread_self() ecs_os_api.thread_self_() + +/* Tasks */ +#define ecs_os_task_new(callback, param) ecs_os_api.task_new_(callback, param) +#define ecs_os_task_join(thread) ecs_os_api.task_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) +#define ecs_os_lainc(value) ecs_os_api.lainc_(value) +#define ecs_os_ladec(value) ecs_os_api.ladec_(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_now() ecs_os_api.now_() +#define ecs_os_get_time(time_out) ecs_os_api.get_time_(time_out) + +/* Logging */ +FLECS_API +void ecs_os_dbg(const char *file, int32_t line, const char *msg); + +FLECS_API +void ecs_os_trace(const char *file, int32_t line, const char *msg); + +FLECS_API +void ecs_os_warn(const char *file, int32_t line, const char *msg); + +FLECS_API +void ecs_os_err(const char *file, int32_t line, const char *msg); + +FLECS_API +void ecs_os_fatal(const char *file, int32_t line, const char *msg); + +FLECS_API +const char* ecs_os_strerror(int err); + +FLECS_API +void ecs_os_strset(char **str, const char *value); + +#ifdef FLECS_ACCURATE_COUNTERS +#define ecs_os_inc(v) (ecs_os_ainc(v)) +#define ecs_os_linc(v) (ecs_os_lainc(v)) +#define ecs_os_dec(v) (ecs_os_adec(v)) +#define ecs_os_ldec(v) (ecs_os_ladec(v)) +#else +#define ecs_os_inc(v) (++(*v)) +#define ecs_os_linc(v) (++(*v)) +#define ecs_os_dec(v) (--(*v)) +#define ecs_os_ldec(v) (--(*v)) +#endif + +#ifdef ECS_TARGET_MINGW +/* mingw bug: without this a conversion error is thrown, but isnan/isinf should + * accept float, double and long double. */ +#define ecs_os_isnan(val) (isnan((float)val)) +#define ecs_os_isinf(val) (isinf((float)val)) +#else +#define ecs_os_isnan(val) (isnan(val)) +#define ecs_os_isinf(val) (isinf(val)) +#endif + +/* 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 task functions available? */ +FLECS_API +bool ecs_os_has_task_support(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 API types + * Public API types. + * + * @{ + */ + +/** + * @defgroup core_types Core API Types + * Types for core API objects. + * + * @{ + */ + +/** Ids are the things that can be added to an entity. + * An id can be an entity or pair, and can have optional id flags. */ +typedef uint64_t ecs_id_t; + +/** An entity identifier. + * Entity ids consist out of a number unique to the entity in the lower 32 bits, + * and a counter used to track entity liveliness in the upper 32 bits. When an + * id is recycled, its generation count is increased. This causes recycled ids + * to be very large (>4 billion), which is normal. */ +typedef ecs_id_t ecs_entity_t; + +/** A type is a list of (component) ids. + * Types are used to communicate the "type" of an entity. In most type systems a + * typeof operation returns a single type. In ECS however, an entity can have + * multiple components, which is why an ECS type consists of a vector of ids. + * + * The component ids of a type are sorted, which ensures that it doesn't matter + * in which order components are added to an entity. For example, if adding + * Position then Velocity would result in type [Position, Velocity], first + * adding Velocity then Position would also result in type [Position, Velocity]. + * + * Entities are grouped together by type in the ECS storage in tables. The + * storage has exactly one table per unique type that is created by the + * application that stores all entities and components for that type. This is + * also referred to as an archetype. + */ +typedef struct { + ecs_id_t *array; + int32_t count; +} ecs_type_t; + +/** A world is the container for all ECS data and supporting features. + * Applications can have multiple worlds, though in most cases will only need + * one. Worlds are isolated from each other, and can have separate sets of + * systems, components, modules etc. + * + * If an application has multiple worlds with overlapping components, it is + * common (though not strictly required) to use the same component ids across + * worlds, which can be achieved by declaring a global component id variable. + * To do this in the C API, see the entities/fwd_component_decl example. The + * C++ API automatically synchronizes component ids between worlds. + * + * Component id conflicts between worlds can occur when a world has already used + * an id for something else. There are a few ways to avoid this: + * + * - Ensure to register the same components in each world, in the same order. + * - Create a dummy world in which all components are preregistered which + * initializes the global id variables. + * + * In some use cases, typically when writing tests, multiple worlds are created + * and deleted with different components, registered in different order. To + * ensure isolation between tests, the C++ API has a `flecs::reset` function + * that forces the API to ignore the old component ids. */ +typedef struct ecs_world_t ecs_world_t; + +/** A table stores entities and components for a specific type. */ +typedef struct ecs_table_t ecs_table_t; + +/** A term is a single element in a query. */ +typedef struct ecs_term_t ecs_term_t; + +/** A filter is an iterable data structure that describes a query. + * Filters are used by the various query implementations in Flecs, like queries, + * observers and rules, to describe a query. Filters themselves can also be + * iterated. */ +typedef struct ecs_filter_t ecs_filter_t; + +/** A query that caches its results. + * Queries are the fastest mechanism for finding and iterating over entities. + * Queries cache results as a list of matching tables (vs. individual entities). + * + * This has several advantages: + * - Matching is only performed when new tables are created, which is infrequent + * - Iterating a query just walks over the cache, no actual searching is needed + * - Iteration is table-based, which allows for direct iteration of underlying + * component arrays, providing good cache locality. + * + * While queries are the fastest mechanism to iterate entities, they are slower + * to create than other mechanisms, as a result of having to build the cache + * first. For this reason queries are best suited for use cases where a single + * query can be reused many times (like is the case for systems). + * + * For ad-hoc queries it is recommended to use filters or rules instead, which + * are slower to iterate, but much faster to create. Applications should at all + * times avoid frequent creation/deletion of queries. */ +typedef struct ecs_query_t ecs_query_t; + +/** A rule is a query with advanced graph traversal features. + * Rules are fast uncached queries with support for advanced graph features such + * as the usage of query variables. A simple example of a rule that matches all + * spaceship entities docked to a planet: + * + * SpaceShip, (DockedTo, $planet), Planet($planet) + * + * Here, the rule traverses the DockedTo relationship, and matches Planet on the + * target of this relationship. Through the usage of variables rules can match + * arbitrary patterns against entity graphs. Other features supported + * exclusively by rules are: + * - Component inheritance + * - Transitivity + * + * Rules have similar iteration performance to filters, but are slower than + * queries. Rules and filters will eventually be merged into a single query + * implementation. Features still lacking for rules are: + * - Up traversal + * - AndFrom, OrFrom, NotFrom operators + */ +typedef struct ecs_rule_t ecs_rule_t; + +/** An observer is a system that is invoked when an event matches its query. + * Observers allow applications to respond to specific events, such as adding or + * removing a component. Observers are created by both specifying a query and + * a list of event kinds that should be listened for. An example of an observer + * that triggers when a Position component is added to an entity (in C++): + * + * @code + * world.observer() + * .event(flecs::OnAdd) + * .each([](Position& p) { + * // called when Position is added to an entity + * }); + * @endcode + * + * Observer queries can be as complex as filters. Observers only trigger when + * the source of the event matches the full observer query. For example, an + * OnAdd observer for Position, Velocity will only trigger after both components + * have been added to the entity. */ +typedef struct ecs_observer_t ecs_observer_t; + +/** An observable produces events that can be listened for by an observer. + * Currently only the world is observable. In the future, queries will become + * observable objects as well. */ +typedef struct ecs_observable_t ecs_observable_t; + +/* Type used for iterating iterable objects. + * Iterators are a common interface across iterable objects (world, filters, + * rules, queries, systems, observers) to provide applications with information + * about the currently iterated result, and to store any state required for the + * iteration. */ +typedef struct ecs_iter_t ecs_iter_t; + +/** A ref is a fast way to fetch a component for a specific entity. + * Refs are a faster alternative to repeatedly calling ecs_get() for the same + * entity/component combination. When comparing the performance of getting a ref + * to calling ecs_get(), a ref is typically 3-5x faster. + * + * Refs achieve this performance by caching internal data structures associated + * with the entity and component on the ecs_ref_t object that otherwise would + * have to be looked up. */ +typedef struct ecs_ref_t ecs_ref_t; + +/** Type hooks are callbacks associated with component lifecycle events. + * Typical examples of lifecycle events are construction, destruction, copying + * and moving of components. */ +typedef struct ecs_type_hooks_t ecs_type_hooks_t; + +/** Type information. + * Contains information about a (component) type, such as its size and + * alignment and type hooks. */ +typedef struct ecs_type_info_t ecs_type_info_t; + +/** Information about an entity, like its table and row. */ +typedef struct ecs_record_t ecs_record_t; + +/** Information about a (component) id, such as type info and tables with the id */ +typedef struct ecs_id_record_t ecs_id_record_t; + +/** Information about where in a table a specific (component) id is stored. */ +typedef struct ecs_table_record_t ecs_table_record_t; + +/** A poly object. + * A poly (short for polymorph) object is an object that has a variable list of + * capabilities, determined by a mixin table. This is the current list of types + * in the flecs API that can be used as an ecs_poly_t: + * + * - ecs_world_t + * - ecs_stage_t + * - ecs_query_t + * - ecs_filter_t + * - ecs_rule_t + * - (more to come) + * + * Functions that accept an ecs_poly_t argument can accept objects of these + * types. If the object does not have the requested mixin the API will throw an + * assert. + * + * The poly/mixin framework enables partially overlapping features to be + * implemented once, and enables objects of different types to interact with + * each other depending on what mixins they have, rather than their type + * (in some ways it's like a mini-ECS). Additionally, each poly object has a + * header that enables the API to do sanity checking on the input arguments. + */ +typedef void ecs_poly_t; + +/** Type that stores poly mixins */ +typedef struct ecs_mixins_t ecs_mixins_t; + +/** Header for ecs_poly_t objects. */ +typedef struct ecs_header_t { + int32_t magic; /* Magic number verifying it's a flecs object */ + int32_t type; /* Magic number indicating which type of flecs object */ + ecs_mixins_t *mixins; /* Table with offsets to (optional) mixins */ +} ecs_header_t; + +/** @} */ + +/** + * @defgroup function_types Function types. + * Function callback types. + * + * @{ + */ + +/** Function prototype for runnables (systems, observers). + * The run callback overrides the default behavior for iterating through the + * results of a runnable object. + * + * The default runnable iterates the iterator, and calls an iter_action (see + * below) for each returned result. + * + * @param it The iterator to be iterated by the runnable. + */ +typedef void (*ecs_run_action_t)( + ecs_iter_t *it); + +/** Function prototype for iterables. + * A system may invoke a callback multiple times, typically once for each + * matched table. + * + * @param it The iterator containing the data for the current match. + */ +typedef void (*ecs_iter_action_t)( + ecs_iter_t *it); + +/** Function prototype for creating an iterator from a poly. + * Used to create iterators from poly objects with the iterable mixin. When a + * filter is provided, an array of two iterators must be passed to the function. + * This allows the mixin implementation to create a chained iterator when + * necessary, which requires two iterator objects. + * + * @param world The world or stage for which to create the iterator. + * @param iterable An iterable poly object. + * @param it The iterator to create (out parameter) + * @param filter Optional term to filter results. + */ +typedef void (*ecs_iter_init_action_t)( + const ecs_world_t *world, + const ecs_poly_t *iterable, + ecs_iter_t *it, + ecs_term_t *filter); + +/** Function prototype for iterating an iterator. + * Stored inside initialized iterators. This allows an application to iterate + * an iterator without needing to know what created it. + * + * @param it The iterator to iterate. + * @return True if iterator has no more results, false if it does. + */ +typedef bool (*ecs_iter_next_action_t)( + ecs_iter_t *it); + +/** Function prototype for freeing an iterator. + * Free iterator resources. + * + * @param it The iterator to free. + */ +typedef void (*ecs_iter_fini_action_t)( + ecs_iter_t *it); + +/** Callback used for comparing components */ +typedef int (*ecs_order_by_action_t)( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); + +/** Callback used for sorting the entire table of components */ +typedef void (*ecs_sort_table_action_t)( + ecs_world_t* world, + ecs_table_t* table, + ecs_entity_t* entities, + void* ptr, + int32_t size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t order_by); + +/** Callback used for grouping tables in a query */ +typedef uint64_t (*ecs_group_by_action_t)( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t group_id, + void *ctx); + +/* Callback invoked when a query creates a new group. */ +typedef void* (*ecs_group_create_action_t)( + ecs_world_t *world, + uint64_t group_id, + void *group_by_ctx); /* from ecs_query_desc_t */ + +/* Callback invoked when a query deletes an existing group. */ +typedef void (*ecs_group_delete_action_t)( + ecs_world_t *world, + uint64_t group_id, + void *group_ctx, /* return value from ecs_group_create_action_t */ + void *group_by_ctx); /* from ecs_query_desc_t */ + +/** 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); + +/** Function to cleanup context data */ +typedef void (*ecs_ctx_free_t)( + void *ctx); + +/** Callback used for sorting values */ +typedef int (*ecs_compare_action_t)( + const void *ptr1, + const void *ptr2); + +/** Callback used for hashing values */ +typedef uint64_t (*ecs_hash_value_action_t)( + const void *ptr); + +/** Constructor/destructor callback */ +typedef void (*ecs_xtor_t)( + void *ptr, + int32_t count, + const ecs_type_info_t *type_info); + +/** Copy is invoked when a component is copied into another component. */ +typedef void (*ecs_copy_t)( + void *dst_ptr, + const void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info); + +/** Move is invoked when a component is moved to another component. */ +typedef void (*ecs_move_t)( + void *dst_ptr, + void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info); + +/* Destructor function for poly objects */ +typedef void (*ecs_poly_dtor_t)( + ecs_poly_t *poly); + +/** @} */ + +/** + * @defgroup mixins Poly mixin types. + * Mixin types for poly mechanism. + * + * @{ + */ + +/** Iterable mixin. + * Allows its container to be iterated. */ +typedef struct ecs_iterable_t { + ecs_iter_init_action_t init; /**< Callback that creates iterator. */ +} ecs_iterable_t; + +/** @} */ + +/** + * @defgroup query_types Query descriptor types. + * Types used to describe queries. + * + * @{ + */ + +/** Specify read/write access for term */ +typedef enum ecs_inout_kind_t { + EcsInOutDefault, /**< InOut for regular terms, In for shared terms */ + EcsInOutNone, /**< Term is neither read nor written */ + EcsInOut, /**< Term is both read and written */ + EcsIn, /**< Term is only read */ + EcsOut, /**< Term is only written */ +} ecs_inout_kind_t; + +/** Specify operator for term */ +typedef enum ecs_oper_kind_t { + EcsAnd, /**< The term must match */ + EcsOr, /**< One of the terms in an or chain must match */ + EcsNot, /**< The term must not match */ + EcsOptional, /**< The term may match */ + EcsAndFrom, /**< Term must match all components from term id */ + EcsOrFrom, /**< Term must match at least one component from term id */ + EcsNotFrom, /**< Term must match none of the components from term id */ +} ecs_oper_kind_t; + +/* Term id flags */ +#define EcsSelf (1u << 1) /**< Match on self */ +#define EcsUp (1u << 2) /**< Match by traversing upwards */ +#define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ +#define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ +#define EcsCascade (1u << 5) /**< Sort results breadth first */ +#define EcsDesc (1u << 6) /**< Iterate groups in descending order */ +#define EcsParent (1u << 7) /**< Short for up(ChildOf) */ +#define EcsIsVariable (1u << 8) /**< Term id is a variable */ +#define EcsIsEntity (1u << 9) /**< Term id is an entity */ +#define EcsIsName (1u << 10) /**< Term id is a name (don't attempt to lookup as entity) */ +#define EcsFilter (1u << 11) /**< Prevent observer from triggering on term */ +#define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsDesc|EcsParent) + +/* Term flags discovered & set during filter creation. Mostly used internally to + * store information relevant to queries. */ +#define EcsTermMatchAny (1u << 0) +#define EcsTermMatchAnySrc (1u << 1) +#define EcsTermSrcFirstEq (1u << 2) +#define EcsTermSrcSecondEq (1u << 3) +#define EcsTermTransitive (1u << 4) +#define EcsTermReflexive (1u << 5) +#define EcsTermIdInherited (1u << 6) +#define EcsTermIsTrivial (1u << 7) +#define EcsTermNoData (1u << 8) + +/* Term flags used for term iteration */ +#define EcsTermMatchDisabled (1u << 7) +#define EcsTermMatchPrefab (1u << 8) + +/** Type that describes a single identifier in a term */ +typedef struct ecs_term_id_t { + ecs_entity_t id; /**< Entity id. If left to 0 and flags does not + * specify whether id is an entity or a variable + * the id will be initialized to EcsThis. + * To explicitly set the id to 0, leave the id + * member to 0 and set EcsIsEntity in flags. */ + + const char *name; /**< Name. This can be either the variable name + * (when the EcsIsVariable flag is set) or an + * entity name. When ecs_term_t::move is true, + * the API assumes ownership over the string and + * will free it when the term is destroyed. */ + + ecs_entity_t trav; /**< Relationship to traverse when looking for the + * component. The relationship must have + * the Traversable property. Default is IsA. */ + + ecs_flags32_t flags; /**< Term flags */ +} ecs_term_id_t; + +/** Type that describes a term (single element in a query) */ +struct ecs_term_t { + ecs_id_t id; /**< Component id to be matched by term. Can be + * set directly, or will be populated from the + * first/second members, which provide more + * flexibility. */ + + ecs_term_id_t src; /**< Source of term */ + ecs_term_id_t first; /**< Component or first element of pair */ + ecs_term_id_t second; /**< Second element of pair */ + + ecs_inout_kind_t inout; /**< Access to contents matched by term */ + ecs_oper_kind_t oper; /**< Operator of term */ + + ecs_id_t id_flags; /**< Id flags of term id */ + char *name; /**< Name of term */ + + int32_t field_index; /**< Index of field for term in iterator */ + ecs_id_record_t *idr; /**< Cached pointer to internal index */ + + ecs_flags16_t flags; /**< Flags that help eval, set by ecs_filter_init */ + + bool move; /**< Used by internals */ +}; + +/** Use $this variable to initialize user-allocated filter object */ +FLECS_API extern ecs_filter_t ECS_FILTER_INIT; + +/** Filters allow for ad-hoc quick filtering of entity tables. */ +struct ecs_filter_t { + ecs_header_t hdr; + + int8_t term_count; /**< Number of elements in terms array */ + int8_t field_count; /**< Number of fields in iterator for filter */ + ecs_flags32_t flags; /**< Filter flags */ + ecs_flags64_t data_fields; /**< Bitset with fields that have data */ + + ecs_term_t *terms; /**< Array containing terms for filter */ + char *variable_names[1]; /**< Placeholder variable names array */ + int32_t *sizes; /**< Field size (same for each result) */ + ecs_id_t *ids; /**< Array with field ids */ + + /* Mixins */ + ecs_entity_t entity; /**< Entity associated with filter (optional) */ + ecs_iterable_t iterable; /**< Iterable mixin */ + ecs_poly_dtor_t dtor; /**< Dtor mixin */ + ecs_world_t *world; /**< World mixin */ +}; + +/* An observer reacts to events matching a filter */ +struct ecs_observer_t { + ecs_header_t hdr; + + ecs_filter_t filter; /**< Query for observer */ + + /* Observer events */ + ecs_entity_t events[FLECS_EVENT_DESC_MAX]; + int32_t event_count; + + ecs_iter_action_t callback; /**< See ecs_observer_desc_t::callback */ + ecs_run_action_t run; /**< See ecs_observer_desc_t::run */ + + void *ctx; /**< Callback context */ + void *binding_ctx; /**< Binding context (for language bindings) */ + + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ + + ecs_observable_t *observable; /**< Observable for observer */ + + int32_t *last_event_id; /**< Last handled event id */ + int32_t last_event_id_storage; + + ecs_id_t register_id; /**< Id observer is registered with (single term observers only) */ + int32_t term_index; /**< Index of the term in parent observer (single term observers only) */ + + bool is_monitor; /**< If true, the observer only triggers when the + * filter did not match with the entity before + * the event happened. */ + + bool is_multi; /**< If true, the observer triggers on more than one term */ + + /* Mixins */ + ecs_poly_dtor_t dtor; +}; + +/** @} */ + +/** Type that contains component lifecycle callbacks. + * + * @ingroup components + */ +struct ecs_type_hooks_t { + ecs_xtor_t ctor; /**< ctor */ + ecs_xtor_t dtor; /**< dtor */ + ecs_copy_t copy; /**< copy assignment */ + ecs_move_t move; /**< move assignment */ + + /** Ctor + copy */ + ecs_copy_t copy_ctor; + + /** Ctor + move */ + ecs_move_t move_ctor; + + /** Ctor + move + dtor (or move_ctor + dtor). + * This combination is typically used when a component is moved from one + * location to a new location, like when it is moved to a new table. If + * not set explicitly it will be derived from other callbacks. */ + ecs_move_t ctor_move_dtor; + + /** Move + dtor. + * This combination is typically used when a component is moved from one + * location to an existing location, like what happens during a remove. If + * not set explicitly it will be derived from other callbacks. */ + ecs_move_t move_dtor; + + /** Callback that is invoked when an instance of a component is added. This + * callback is invoked before triggers are invoked. */ + ecs_iter_action_t on_add; + + /** Callback that is invoked when an instance of the component is set. This + * callback is invoked before triggers are invoked, and enable the component + * to respond to changes on itself before others can. */ + ecs_iter_action_t on_set; + + /** Callback that is invoked when an instance of the component is removed. + * This callback is invoked after the triggers are invoked, and before the + * destructor is invoked. */ + ecs_iter_action_t on_remove; + + void *ctx; /**< User defined context */ + void *binding_ctx; /**< Language binding context */ + + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ +}; + +/** Type that contains component information (passed to ctors/dtors/...) + * + * @ingroup components + */ +struct ecs_type_info_t { + ecs_size_t size; /**< Size of type */ + ecs_size_t alignment; /**< Alignment of type */ + ecs_type_hooks_t hooks; /**< Type hooks */ + ecs_entity_t component; /**< Handle to component (do not set) */ + const char *name; /**< Type name. */ +}; + +/** + * @file api_types.h + * @brief Supporting types for the public API. + * + * This file contains types 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; + +/** Table data */ +typedef struct ecs_data_t ecs_data_t; + +/* Switch list */ +typedef struct ecs_switch_t ecs_switch_t; + +/* Cached query table data */ +typedef struct ecs_query_table_match_t ecs_query_table_match_t; + +//////////////////////////////////////////////////////////////////////////////// +//// Non-opaque types +//////////////////////////////////////////////////////////////////////////////// + +/** Mixin for emitting events to triggers/observers */ +/** All observers for a specific event */ +typedef struct ecs_event_record_t { + struct ecs_event_id_record_t *any; + struct ecs_event_id_record_t *wildcard; + struct ecs_event_id_record_t *wildcard_pair; + ecs_map_t event_ids; /* map */ + ecs_entity_t event; +} ecs_event_record_t; + +struct ecs_observable_t { + ecs_event_record_t on_add; + ecs_event_record_t on_remove; + ecs_event_record_t on_set; + ecs_event_record_t un_set; + ecs_event_record_t on_wildcard; + ecs_sparse_t events; /* sparse */ +}; + +/** Record for entity index */ +struct ecs_record_t { + ecs_id_record_t *idr; /* Id record to (*, entity) for target entities */ + ecs_table_t *table; /* Identifies a type (and table) in world */ + uint32_t row; /* Table row of the entity */ + int32_t dense; /* Index in dense array */ +}; + +/** Range in table */ +typedef struct ecs_table_range_t { + ecs_table_t *table; + int32_t offset; /* Leave both members to 0 to cover entire table */ + int32_t count; +} ecs_table_range_t; + +/** Value of query variable */ +typedef struct ecs_var_t { + ecs_table_range_t range; /* Set when variable stores a range of entities */ + ecs_entity_t entity; /* Set when variable stores single entity */ + + /* Most entities can be stored as a range by setting range.count to 1, + * however in order to also be able to store empty entities in variables, + * a separate entity member is needed. Both range and entity may be set at + * the same time, as long as they are consistent. */ +} ecs_var_t; + +/** Cached reference. */ +struct ecs_ref_t { + ecs_entity_t entity; /* Entity */ + ecs_entity_t id; /* Component id */ + uint64_t table_id; /* Table id for detecting ABA issues */ + struct ecs_table_record_t *tr; /* Table record for component */ + ecs_record_t *record; /* Entity index record */ +}; + +/* Cursor to stack allocator. Type is public to allow for white box testing. */ +struct ecs_stack_page_t; + +typedef struct ecs_stack_cursor_t { + struct ecs_stack_cursor_t *prev; + struct ecs_stack_page_t *page; + int16_t sp; + bool is_free; +#ifdef FLECS_DEBUG + struct ecs_stack_t *owner; +#endif +} ecs_stack_cursor_t; + +/* Page-iterator specific data */ +typedef struct ecs_page_iter_t { + int32_t offset; + int32_t limit; + int32_t remaining; +} ecs_page_iter_t; + +/* Worker-iterator specific data */ +typedef struct ecs_worker_iter_t { + int32_t index; + int32_t count; +} ecs_worker_iter_t; + +/* Convenience struct to iterate table array for id */ +typedef struct ecs_table_cache_iter_t { + struct ecs_table_cache_hdr_t *cur, *next; + struct ecs_table_cache_hdr_t *next_list; +} ecs_table_cache_iter_t; + +/** Term-iterator specific data */ +typedef struct ecs_term_iter_t { + ecs_term_t term; + ecs_id_record_t *self_index; + ecs_id_record_t *set_index; + + ecs_id_record_t *cur; + ecs_table_cache_iter_t it; + int32_t index; + int32_t observed_table_count; + + ecs_table_t *table; + int32_t cur_match; + int32_t match_count; + int32_t last_column; + + bool empty_tables; + + /* Storage */ + ecs_id_t id; + int32_t column; + ecs_entity_t subject; + ecs_size_t size; + void *ptr; +} ecs_term_iter_t; + +typedef enum ecs_iter_kind_t { + EcsIterEvalCondition, + EcsIterEvalTables, + EcsIterEvalChain, + EcsIterEvalNone +} ecs_iter_kind_t; + +/** Filter-iterator specific data */ +typedef struct ecs_filter_iter_t { + const ecs_filter_t *filter; + ecs_iter_kind_t kind; + ecs_term_iter_t term_iter; + int32_t matches_left; + int32_t pivot_term; +} ecs_filter_iter_t; + +/** Query-iterator specific data */ +typedef struct ecs_query_iter_t { + ecs_query_t *query; + ecs_query_table_match_t *node, *prev, *last; + int32_t sparse_smallest; + int32_t sparse_first; + int32_t bitset_first; + int32_t skip_count; +} ecs_query_iter_t; + +/** Snapshot-iterator specific data */ +typedef struct ecs_snapshot_iter_t { + ecs_filter_t filter; + ecs_vec_t tables; /* ecs_table_leaf_t */ + int32_t index; +} ecs_snapshot_iter_t; + +typedef struct ecs_rule_op_profile_t { + int32_t count[2]; /* 0 = enter, 1 = redo */ +} ecs_rule_op_profile_t; + +/** Rule-iterator specific data */ +typedef struct ecs_rule_iter_t { + const ecs_rule_t *rule; + struct ecs_var_t *vars; /* Variable storage */ + const struct ecs_rule_var_t *rule_vars; + const struct ecs_rule_op_t *ops; + struct ecs_rule_op_ctx_t *op_ctx; /* Operation-specific state */ + uint64_t *written; + ecs_flags32_t source_set; + +#ifdef FLECS_DEBUG + ecs_rule_op_profile_t *profile; +#endif + + int16_t op; + int16_t sp; +} ecs_rule_iter_t; + +/* Bits for tracking whether a cache was used/whether the array was allocated. + * Used by flecs_iter_init, flecs_iter_validate and ecs_iter_fini. + * Constants are named to enable easy macro substitution. */ +#define flecs_iter_cache_ids (1u << 0u) +#define flecs_iter_cache_columns (1u << 1u) +#define flecs_iter_cache_sources (1u << 2u) +#define flecs_iter_cache_ptrs (1u << 3u) +#define flecs_iter_cache_match_indices (1u << 4u) +#define flecs_iter_cache_variables (1u << 5u) +#define flecs_iter_cache_all (255) + +/* Inline iterator arrays to prevent allocations for small array sizes */ +typedef struct ecs_iter_cache_t { + ecs_stack_cursor_t *stack_cursor; /* Stack cursor to restore to */ + ecs_flags8_t used; /* For which fields is the cache used */ + ecs_flags8_t allocated; /* Which fields are allocated */ +} ecs_iter_cache_t; + +/* Private iterator data. Used by iterator implementations to keep track of + * progress & to provide builtin storage. */ +typedef struct ecs_iter_private_t { + union { + ecs_term_iter_t term; + ecs_filter_iter_t filter; + ecs_query_iter_t query; + ecs_rule_iter_t rule; + ecs_snapshot_iter_t snapshot; + ecs_page_iter_t page; + ecs_worker_iter_t worker; + } iter; /* Iterator specific data */ + + void *entity_iter; /* Filter applied after matching a table */ + ecs_iter_cache_t cache; /* Inline arrays to reduce allocations */ +} ecs_iter_private_t; + +/** Iterator */ +struct ecs_iter_t { + /* World */ + ecs_world_t *world; /* The world */ + ecs_world_t *real_world; /* Actual world. This differs from world when in readonly mode */ + + /* Matched data */ + ecs_entity_t *entities; /* Entity identifiers */ + void **ptrs; /* Pointers to components. Array if from this, pointer if not. */ + ecs_size_t *sizes; /* Component sizes */ + ecs_table_t *table; /* Current table */ + ecs_table_t *other_table; /* Prev or next table when adding/removing */ + ecs_id_t *ids; /* (Component) ids */ + ecs_var_t *variables; /* Values of variables (if any) */ + int32_t *columns; /* Query term to table column mapping */ + ecs_entity_t *sources; /* Entity on which the id was matched (0 if same as entities) */ + int32_t *match_indices; /* Indices of current match for term. Allows an iterator to iterate + * all permutations of wildcards in query. */ + ecs_ref_t *references; /* Cached refs to components (if iterating a cache) */ + ecs_flags64_t constrained_vars; /* Bitset that marks constrained variables */ + uint64_t group_id; /* Group id for table, if group_by is used */ + int32_t field_count; /* Number of fields in iterator */ + + /* Input information */ + ecs_entity_t system; /* The system (if applicable) */ + ecs_entity_t event; /* The event (if applicable) */ + ecs_id_t event_id; /* The (component) id for the event */ + int32_t event_cur; /* Unique event id. Used to dedup observer calls */ + + /* Query information */ + const ecs_filter_t *query; /* Query being evaluated */ + ecs_term_t *terms; /* Term array of query being evaluated */ + int32_t table_count; /* Active table count for query */ + int32_t term_index; /* Index of term that emitted an event. + * This field will be set to the 'index' field + * of an observer term. */ + int32_t variable_count; /* Number of variables for query */ + char **variable_names; /* Names of variables (if any) */ + + /* Context */ + void *param; /* Param passed to ecs_run */ + void *ctx; /* System context */ + void *binding_ctx; /* Binding context */ + + /* Time */ + ecs_ftime_t delta_time; /* Time elapsed since last frame */ + ecs_ftime_t delta_system_time;/* Time elapsed since last system invocation */ + + /* Iterator counters */ + int32_t frame_offset; /* Offset relative to start of iteration */ + int32_t offset; /* Offset relative to current table */ + int32_t count; /* Number of entities to iterate */ + int32_t instance_count; /* Number of entities to iterate before next table */ + + /* Iterator flags */ + ecs_flags32_t flags; + + ecs_entity_t interrupted_by; /* When set, system execution is interrupted */ + + ecs_iter_private_t priv; /* Private data */ + + /* Chained iterators */ + ecs_iter_next_action_t next; /* Function to progress iterator */ + ecs_iter_action_t callback; /* Callback of system or observer */ + ecs_iter_action_t set_var; /* Invoked after setting variable (optionally set) */ + ecs_iter_fini_action_t fini; /* Function to cleanup iterator resources */ + ecs_iter_t *chain_it; /* Optional, allows for creating iterator chains */ +}; + +#ifdef __cplusplus +} +#endif + +#endif + + +/** + * @file api_support.h + * @brief Support functions and constants. + * + * 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. + */ + +#ifndef FLECS_API_SUPPORT_H +#define FLECS_API_SUPPORT_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/** This is the largest possible component id. Components for the most part + * occupy the same id range as entities, however they are not allowed to overlap + * with (8) bits reserved for id flags. */ +#define ECS_MAX_COMPONENT_ID (~((uint32_t)(ECS_ID_FLAGS_MASK >> 32))) + +/** The maximum number of nested function calls before the core will throw a + * cycle detected error */ +#define ECS_MAX_RECURSION (512) + +/** Maximum length of a parser token (used by parser-related addons) */ +#define ECS_MAX_TOKEN_SIZE (256) + +//////////////////////////////////////////////////////////////////////////////// +//// Global type handles +//////////////////////////////////////////////////////////////////////////////// + +/** This allows passing 0 as type to functions that accept ids */ +#define FLECS_ID0ID_ 0 + +FLECS_API +char* ecs_module_path_from_c( + const char *c_name); + +bool ecs_identifier_is_0( + const char *id); + +/* Constructor that zeromem's a component value */ +FLECS_API +void ecs_default_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ctx); + +/* Create allocated string from format */ +FLECS_DBG_API +char* ecs_vasprintf( + const char *fmt, + va_list args); + +/* Create allocated string from format */ +FLECS_API +char* ecs_asprintf( + const char *fmt, + ...); + +/* Convert identifier to snake case */ +FLECS_API +char* flecs_to_snake_case( + const char *str); + +FLECS_DBG_API +int32_t flecs_table_observed_count( + const ecs_table_t *table); + +FLECS_DBG_API +void flecs_dump_backtrace( + void *stream); + +/** Calculate offset from address */ +#ifdef __cplusplus +#define ECS_OFFSET(o, offset) reinterpret_cast((reinterpret_cast(o)) + (static_cast(offset))) +#else +#define ECS_OFFSET(o, offset) (void*)(((uintptr_t)(o)) + ((uintptr_t)(offset))) +#endif +#define ECS_OFFSET_T(o, T) ECS_OFFSET(o, ECS_SIZEOF(T)) + +#define ECS_ELEM(ptr, size, index) ECS_OFFSET(ptr, (size) * (index)) +#define ECS_ELEM_T(o, T, index) ECS_ELEM(o, ECS_SIZEOF(T), index) + +/** Enable/disable bitsets */ +#define ECS_BIT_SET(flags, bit) (flags) |= (bit) +#define ECS_BIT_CLEAR(flags, bit) (flags) &= ~(bit) +#define ECS_BIT_COND(flags, bit, cond) ((cond) \ + ? (ECS_BIT_SET(flags, bit)) \ + : (ECS_BIT_CLEAR(flags, bit))) +#define ECS_BIT_IS_SET(flags, bit) ((flags) & (bit)) + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file hashmap.h + * @brief Hashmap data structure. + */ + +#ifndef FLECS_HASHMAP_H +#define FLECS_HASHMAP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + ecs_vec_t keys; + ecs_vec_t values; +} ecs_hm_bucket_t; + +typedef struct { + ecs_hash_value_action_t hash; + ecs_compare_action_t compare; + ecs_size_t key_size; + ecs_size_t value_size; + ecs_block_allocator_t *hashmap_allocator; + ecs_block_allocator_t bucket_allocator; + ecs_map_t impl; +} ecs_hashmap_t; + +typedef struct { + ecs_map_iter_t it; + ecs_hm_bucket_t *bucket; + int32_t index; +} flecs_hashmap_iter_t; + +typedef struct { + void *key; + void *value; + uint64_t hash; +} flecs_hashmap_result_t; + +FLECS_DBG_API +void flecs_hashmap_init_( + ecs_hashmap_t *hm, + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare, + ecs_allocator_t *allocator); + +#define flecs_hashmap_init(hm, K, V, hash, compare, allocator)\ + flecs_hashmap_init_(hm, ECS_SIZEOF(K), ECS_SIZEOF(V), hash, compare, allocator) + +FLECS_DBG_API +void flecs_hashmap_fini( + ecs_hashmap_t *map); + +FLECS_DBG_API +void* flecs_hashmap_get_( + const ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size); + +#define flecs_hashmap_get(map, key, V)\ + (V*)flecs_hashmap_get_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) + +FLECS_DBG_API +flecs_hashmap_result_t flecs_hashmap_ensure_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size); + +#define flecs_hashmap_ensure(map, key, V)\ + flecs_hashmap_ensure_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) + +FLECS_DBG_API +void flecs_hashmap_set_( + ecs_hashmap_t *map, + ecs_size_t key_size, + void *key, + ecs_size_t value_size, + const void *value); + +#define flecs_hashmap_set(map, key, value)\ + flecs_hashmap_set_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(*value), value) + +FLECS_DBG_API +void flecs_hashmap_remove_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size); + +#define flecs_hashmap_remove(map, key, V)\ + flecs_hashmap_remove_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) + +FLECS_DBG_API +void flecs_hashmap_remove_w_hash_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size, + uint64_t hash); + +#define flecs_hashmap_remove_w_hash(map, key, V, hash)\ + flecs_hashmap_remove_w_hash_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V), hash) + +FLECS_DBG_API +ecs_hm_bucket_t* flecs_hashmap_get_bucket( + const ecs_hashmap_t *map, + uint64_t hash); + +FLECS_DBG_API +void flecs_hm_bucket_remove( + ecs_hashmap_t *map, + ecs_hm_bucket_t *bucket, + uint64_t hash, + int32_t index); + +FLECS_DBG_API +void flecs_hashmap_copy( + ecs_hashmap_t *dst, + const ecs_hashmap_t *src); + +FLECS_DBG_API +flecs_hashmap_iter_t flecs_hashmap_iter( + ecs_hashmap_t *map); + +FLECS_DBG_API +void* flecs_hashmap_next_( + flecs_hashmap_iter_t *it, + ecs_size_t key_size, + void *key_out, + ecs_size_t value_size); + +#define flecs_hashmap_next(map, V)\ + (V*)flecs_hashmap_next_(map, 0, NULL, ECS_SIZEOF(V)) + +#define flecs_hashmap_next_w_key(map, K, key, V)\ + (V*)flecs_hashmap_next_(map, ECS_SIZEOF(K), key, ECS_SIZEOF(V)) + +#ifdef __cplusplus +} +#endif + +#endif + + +/** Used with ecs_entity_init(). + * + * @ingroup entities + */ +typedef struct ecs_entity_desc_t { + int32_t _canary; + + ecs_entity_t id; /**< Set to modify existing entity (optional) */ + + const char *name; /**< Name of the entity. If no entity is provided, an + * entity with this name will be looked up first. When + * an entity is provided, the name will be verified + * with the existing entity. */ + + const char *sep; /**< Optional custom separator for hierarchical names. + * Leave to NULL for default ('.') separator. Set to + * an empty string to prevent tokenization of name. */ + + const char *root_sep; /**< Optional, used for identifiers relative to root */ + + const char *symbol; /**< Optional entity symbol. A symbol is an unscoped + * identifier that can be used to lookup an entity. The + * primary use case for this is to associate the entity + * with a language identifier, such as a type or + * function name, where these identifiers differ from + * the name they are registered with in flecs. For + * example, C type "EcsPosition" might be registered + * as "flecs.components.transform.Position", with the + * symbol set to "EcsPosition". */ + + bool use_low_id; /**< When set to true, a low id (typically reserved for + * components) will be used to create the entity, if + * no id is specified. */ + + /** Array of ids to add to the new or existing entity. */ + ecs_id_t add[FLECS_ID_DESC_MAX]; + + /** String expression with components to add */ + const char *add_expr; +} ecs_entity_desc_t; + +/** Used with ecs_bulk_init(). + * + * @ingroup entities + */ +typedef struct ecs_bulk_desc_t { + int32_t _canary; + + ecs_entity_t *entities; /**< Entities to bulk insert. Entity ids provided by + * the application must be empty (cannot + * have components). If no entity ids are provided, the + * operation will create 'count' new entities. */ + + int32_t count; /**< Number of entities to create/populate */ + + ecs_id_t ids[FLECS_ID_DESC_MAX]; /**< Ids to create the entities with */ + + void **data; /**< Array with component data to insert. Each element in + * the array must correspond with an element in the ids + * array. If an element in the ids array is a tag, the + * data array must contain a NULL. An element may be + * set to NULL for a component, in which case the + * component will not be set by the operation. */ + + ecs_table_t *table; /**< Table to insert the entities into. Should not be set + * at the same time as ids. When 'table' is set at the + * same time as 'data', the elements in the data array + * must correspond with the ids in the table's type. */ + +} ecs_bulk_desc_t; + +/** Used with ecs_component_init(). + * + * @ingroup components + */ +typedef struct ecs_component_desc_t { + int32_t _canary; + + /** Existing entity to associate with observer (optional) */ + ecs_entity_t entity; + + /** Parameters for type (size, hooks, ...) */ + ecs_type_info_t type; +} ecs_component_desc_t; + +/** Used with ecs_filter_init(). + * + * @ingroup filters + */ +typedef struct ecs_filter_desc_t { + int32_t _canary; + + /** Terms of the filter. If a filter has more terms than + * FLECS_TERM_DESC_MAX use terms_buffer */ + ecs_term_t terms[FLECS_TERM_DESC_MAX]; + + /** For filters with lots of terms an outside array can be provided. */ + ecs_term_t *terms_buffer; + + /** Number of terms in array provided in terms_buffer. */ + int32_t terms_buffer_count; + + /** External storage to prevent allocation of the filter object */ + ecs_filter_t *storage; + + /** When true, terms returned by an iterator may either contain 1 or N + * elements, where terms with N elements are owned, and terms with 1 element + * are shared, for example from a parent or base entity. When false, the + * iterator will at most return 1 element when the result contains both + * owned and shared terms. */ + bool instanced; + + /** Flags for advanced usage */ + ecs_flags32_t flags; + + /** Filter expression. Should not be set at the same time as terms array */ + const char *expr; + + /** Entity associated with query (optional) */ + ecs_entity_t entity; +} ecs_filter_desc_t; + +/** Used with ecs_query_init(). + * + * @ingroup queries + */ +typedef struct ecs_query_desc_t { + int32_t _canary; + + /** Filter for the query */ + ecs_filter_desc_t filter; + + /** Component to be used by order_by */ + ecs_entity_t order_by_component; + + /** Callback used for ordering query results. If order_by_id is 0, the + * pointer provided to the callback will be NULL. If the callback is not + * set, results will not be ordered. */ + ecs_order_by_action_t order_by; + + /** Callback used for ordering query results. Same as order_by, + * but more efficient. */ + ecs_sort_table_action_t sort_table; + + /** Id to be used by group_by. This id is passed to the group_by function and + * can be used identify the part of an entity type that should be used for + * grouping. */ + ecs_id_t group_by_id; + + /** Callback used for grouping results. If the callback is not set, results + * will not be grouped. When set, this callback will be used to calculate a + * "rank" for each entity (table) based on its components. This rank is then + * used to sort entities (tables), so that entities (tables) of the same + * rank are "grouped" together when iterated. */ + ecs_group_by_action_t group_by; + + /** Callback that is invoked when a new group is created. The return value of + * the callback is stored as context for a group. */ + ecs_group_create_action_t on_group_create; + + /** Callback that is invoked when an existing group is deleted. The return + * value of the on_group_create callback is passed as context parameter. */ + ecs_group_delete_action_t on_group_delete; + + /** Context to pass to group_by */ + void *group_by_ctx; + + /** Function to free group_by_ctx */ + ecs_ctx_free_t group_by_ctx_free; + + /** If set, the query will be created as a subquery. A subquery matches at + * most a subset of its parent query. Subqueries do not directly receive + * (table) notifications from the world. Instead parent queries forward + * results to subqueries. This can improve matching performance, as fewer + * queries need to be matched with new tables. + * Subqueries can be nested. */ + ecs_query_t *parent; + + /** User context to pass to callback */ + void *ctx; + + /** Context to be used for language bindings */ + void *binding_ctx; + + /** Callback to free ctx */ + ecs_ctx_free_t ctx_free; + + /** Callback to free binding_ctx */ + ecs_ctx_free_t binding_ctx_free; +} ecs_query_desc_t; + +/** Used with ecs_observer_init(). + * + * @ingroup observers + */ +typedef struct ecs_observer_desc_t { + int32_t _canary; + + /** Existing entity to associate with observer (optional) */ + ecs_entity_t entity; + + /** Filter for observer */ + ecs_filter_desc_t filter; + + /** Events to observe (OnAdd, OnRemove, OnSet, UnSet) */ + ecs_entity_t events[FLECS_EVENT_DESC_MAX]; + + /** When observer is created, generate events from existing data. For example, + * EcsOnAdd Position would match all existing instances of Position. + * This is only supported for events that are iterable (see EcsIterable) */ + bool yield_existing; + + /** Callback to invoke on an event, invoked when the observer matches. */ + ecs_iter_action_t callback; + + /** Callback invoked on an event. When left to NULL the default runner + * is used which matches the event with the observer's filter, and calls + * 'callback' when it matches. + * A reason to override the run function is to improve performance, if there + * are more efficient way to test whether an event matches the observer than + * the general purpose query matcher. */ + ecs_run_action_t run; + + /** User context to pass to callback */ + void *ctx; + + /** Context to be used for language bindings */ + void *binding_ctx; + + /** Callback to free ctx */ + ecs_ctx_free_t ctx_free; + + /** Callback to free binding_ctx */ + ecs_ctx_free_t binding_ctx_free; + + /** Observable with which to register the observer */ + ecs_poly_t *observable; + + /** Optional shared last event id for multiple observers. Ensures only one + * of the observers with the shared id gets triggered for an event */ + int32_t *last_event_id; + + /** Used for internal purposes */ + int32_t term_index; +} ecs_observer_desc_t; + +/** Used with ecs_emit(). + * + * @ingroup observers + */ +typedef struct ecs_event_desc_t { + /** The event id. Only observers for the specified event will be notified */ + ecs_entity_t event; + + /** Component ids. Only observers with a matching component id will be + * notified. Observers are guaranteed to get notified once, even if they + * match more than one id. */ + const ecs_type_t *ids; + + /** The table for which to notify. */ + ecs_table_t *table; + + /** Optional 2nd table to notify. This can be used to communicate the + * previous or next table, in case an entity is moved between tables. */ + ecs_table_t *other_table; + + /** Limit notified entities to ones starting from offset (row) in table */ + int32_t offset; + + /** Limit number of notified entities to count. offset+count must be less + * than the total number of entities in the table. If left to 0, it will be + * automatically determined by doing ecs_table_count(table) - offset. */ + int32_t count; + + /** Single-entity alternative to setting table / offset / count */ + ecs_entity_t entity; + + /** Optional context. + * The type of the param must be the event, where the event is a component. + * When an event is enqueued, the value of param is coped to a temporary + * storage of the event type. */ + void *param; + + /* Same as param, but with the guarantee that the value won't be modified. + * When an event with a const parameter is enqueued, the value of the param + * is copied to a temporary storage of the event type. */ + const void *const_param; + + /** Observable (usually the world) */ + ecs_poly_t *observable; + + /** Event flags */ + ecs_flags32_t flags; +} ecs_event_desc_t; + + +/** + * @defgroup misc_types Miscellaneous types + * Types used to create entities, observers, queries and more. + * + * @{ + */ + +/* Utility to hold a value of a dynamic type */ +typedef struct ecs_value_t { + ecs_entity_t type; + void *ptr; +} ecs_value_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 min_id; /**< First allowed entity id */ + ecs_entity_t max_id; /**< Last allowed entity id */ + + ecs_ftime_t delta_time_raw; /**< Raw delta time (no time scaling) */ + ecs_ftime_t delta_time; /**< Time passed to or computed by ecs_progress */ + ecs_ftime_t time_scale; /**< Time scale applied to delta_time */ + ecs_ftime_t target_fps; /**< Target fps */ + ecs_ftime_t frame_time_total; /**< Total time spent processing a frame */ + ecs_ftime_t system_time_total; /**< Total time spent in systems */ + ecs_ftime_t emit_time_total; /**< Total time spent notifying observers */ + ecs_ftime_t merge_time_total; /**< Total time spent in merges */ + ecs_ftime_t world_time_total; /**< Time elapsed in simulation */ + ecs_ftime_t world_time_total_raw; /**< Time elapsed in simulation (no scaling) */ + ecs_ftime_t rematch_time_total; /**< Time spent on query rematching */ + + int64_t frame_count_total; /**< Total number of frames */ + int64_t merge_count_total; /**< Total number of merges */ + int64_t rematch_count_total; /**< Total number of rematches */ + + int64_t id_create_total; /**< Total number of times a new id was created */ + int64_t id_delete_total; /**< Total number of times an id was deleted */ + int64_t table_create_total; /**< Total number of times a table was created */ + int64_t table_delete_total; /**< Total number of times a table was deleted */ + int64_t pipeline_build_count_total; /**< Total number of pipeline builds */ + int64_t systems_ran_frame; /**< Total number of systems ran in last frame */ + int64_t observers_ran_frame; /**< Total number of times observer was invoked */ + + int32_t tag_id_count; /**< Number of tag (no data) ids in the world */ + int32_t component_id_count; /**< Number of component (data) ids in the world */ + int32_t pair_id_count; /**< Number of pair ids in the world */ + + int32_t table_count; /**< Number of tables */ + int32_t empty_table_count; /**< Number of tables without entities */ + + /* -- Command counts -- */ + struct { + int64_t add_count; /**< add commands processed */ + int64_t remove_count; /**< remove commands processed */ + int64_t delete_count; /**< delete commands processed */ + int64_t clear_count; /**< clear commands processed */ + int64_t set_count; /**< set commands processed */ + int64_t get_mut_count; /**< get_mut/emplace commands processed */ + int64_t modified_count; /**< modified commands processed */ + int64_t other_count; /**< other commands processed */ + int64_t discard_count; /**< commands discarded, happens when entity is no longer alive when running the command */ + int64_t batched_entity_count; /**< entities for which commands were batched */ + int64_t batched_command_count; /**< commands batched */ + } cmd; + + const char *name_prefix; /**< Value set by ecs_set_name_prefix(). Used + * to remove library prefixes of symbol + * names (such as `Ecs`, `ecs_`) when + * registering them as names. */ +} ecs_world_info_t; + +/** Type that contains information about a query group. */ +typedef struct ecs_query_group_info_t { + int32_t match_count; /**< How often tables have been matched/unmatched */ + int32_t table_count; /**< Number of tables in group */ + void *ctx; /**< Group context, returned by on_group_create */ +} ecs_query_group_info_t; + +/** @} */ + +/** + * @defgroup builtin_components Builtin component types. + * Types that represent builtin components. + * + * @{ + */ + +/** A (string) identifier. Used as pair with EcsName and EcsSymbol tags */ +typedef struct EcsIdentifier { + char *value; /**< Identifier string */ + ecs_size_t length; /**< Length of identifier */ + uint64_t hash; /**< Hash of current value */ + uint64_t index_hash; /**< Hash of existing record in current index */ + ecs_hashmap_t *index; /**< Current index */ +} EcsIdentifier; + +/** Component information. */ +typedef struct EcsComponent { + ecs_size_t size; /**< Component size */ + ecs_size_t alignment; /**< Component alignment */ +} EcsComponent; + +/** Component for storing a poly object */ +typedef struct EcsPoly { + ecs_poly_t *poly; /**< Pointer to poly object */ +} EcsPoly; + +/** Target data for flattened relationships. */ +typedef struct EcsTarget { + int32_t count; + ecs_record_t *target; +} EcsTarget; + +/** Component for iterable entities */ +typedef ecs_iterable_t EcsIterable; + +/** @} */ +/** @} */ + +/* Only include deprecated definitions if deprecated addon is required */ +#ifdef FLECS_DEPRECATED +/** + * @file addons/deprecated.h + * @brief The deprecated addon contains deprecated operations. + */ + +#ifdef FLECS_DEPRECATED + +#ifndef FLECS_DEPRECATED_H +#define FLECS_DEPRECATED_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +#endif + +/** + * @defgroup api_constants API Constants + * Public API constants. + * + * @{ + */ + +/** + * @defgroup id_flags Component id flags. + * Id flags are bits that can be set on an id (ecs_id_t). + * + * @{ + */ + +/** Indicates that the id is a pair. */ +FLECS_API extern const ecs_id_t ECS_PAIR; + +/** Automatically override component when it is inherited */ +FLECS_API extern const ecs_id_t ECS_OVERRIDE; + +/** Adds bitset to storage which allows component to be enabled/disabled */ +FLECS_API extern const ecs_id_t ECS_TOGGLE; + +/** Include all components from entity to which AND is applied */ +FLECS_API extern const ecs_id_t ECS_AND; + +/** @} */ + +/** + * @defgroup builtin_tags Builtin component ids. + * @{ + */ + +/* Builtin component ids */ +FLECS_API extern const ecs_entity_t ecs_id(EcsComponent); +FLECS_API extern const ecs_entity_t ecs_id(EcsIdentifier); +FLECS_API extern const ecs_entity_t ecs_id(EcsIterable); +FLECS_API extern const ecs_entity_t ecs_id(EcsPoly); + +FLECS_API extern const ecs_entity_t EcsQuery; +FLECS_API extern const ecs_entity_t EcsObserver; + +/* System module component ids */ +FLECS_API extern const ecs_entity_t EcsSystem; +FLECS_API extern const ecs_entity_t ecs_id(EcsTickSource); + +/* Pipeline module component ids */ +FLECS_API extern const ecs_entity_t ecs_id(EcsPipelineQuery); + +/* Timer module component ids */ +FLECS_API extern const ecs_entity_t ecs_id(EcsTimer); +FLECS_API extern const ecs_entity_t ecs_id(EcsRateFilter); + +/** Root scope for builtin flecs entities */ +FLECS_API extern const ecs_entity_t EcsFlecs; + +/** Core module scope */ +FLECS_API extern const ecs_entity_t EcsFlecsCore; + +/** Entity associated with world (used for "attaching" components to world) */ +FLECS_API extern const ecs_entity_t EcsWorld; + +/** Wildcard entity ("*"). Matches any id, returns all matches. */ +FLECS_API extern const ecs_entity_t EcsWildcard; + +/** Any entity ("_"). Matches any id, returns only the first. */ +FLECS_API extern const ecs_entity_t EcsAny; + +/** This entity. Default source for queries. */ +FLECS_API extern const ecs_entity_t EcsThis; + +/** Variable entity ("$"). Used in expressions to prefix variable names */ +FLECS_API extern const ecs_entity_t EcsVariable; + +/** Marks a relationship as transitive. + * Behavior: + * if R(X, Y) and R(Y, Z) then R(X, Z) + */ +FLECS_API extern const ecs_entity_t EcsTransitive; + +/** Marks a relationship as reflexive. + * Behavior: + * R(X, X) == true + */ +FLECS_API extern const ecs_entity_t EcsReflexive; + +/** Ensures that entity/component cannot be used as target in IsA relationship. + * Final can improve the performance of rule-based queries, as they will not + * attempt to substitute a final component with its subsets. + * + * Behavior: + * if IsA(X, Y) and Final(Y) throw error + */ +FLECS_API extern const ecs_entity_t EcsFinal; + +/** Ensures that component is never inherited from an IsA target. + * + * Behavior: + * if DontInherit(X) and X(B) and IsA(A, B) then X(A) is false. + */ +FLECS_API extern const ecs_entity_t EcsDontInherit; + +/** Ensures a component is always overridden. + * + * Behavior: + * As if the component is added together with OVERRIDE | T + */ +FLECS_API extern const ecs_entity_t EcsAlwaysOverride; + +/** Marks relationship as commutative. + * Behavior: + * if R(X, Y) then R(Y, X) + */ +FLECS_API extern const ecs_entity_t EcsSymmetric; + +/** Can be added to relationship to indicate that the relationship can only occur + * once on an entity. Adding a 2nd instance will replace the 1st. + * + * Behavior: + * R(X, Y) + R(X, Z) = R(X, Z) + */ +FLECS_API extern const ecs_entity_t EcsExclusive; + +/** Marks a relationship as acyclic. Acyclic relationships may not form cycles. */ +FLECS_API extern const ecs_entity_t EcsAcyclic; + +/** Marks a relationship as traversable. Traversable relationships may be + * traversed with "up" queries. Traversable relationships are acyclic. */ +FLECS_API extern const ecs_entity_t EcsTraversable; + +/** Ensure that a component always is added together with another component. + * + * Behavior: + * If With(R, O) and R(X) then O(X) + * If With(R, O) and R(X, Y) then O(X, Y) + */ +FLECS_API extern const ecs_entity_t EcsWith; + +/** Ensure that relationship target is child of specified entity. + * + * Behavior: + * If OneOf(R, O) and R(X, Y), Y must be a child of O + * If OneOf(R) and R(X, Y), Y must be a child of R + */ +FLECS_API extern const ecs_entity_t EcsOneOf; + +/** Can be added to relationship to indicate that it should never hold data, + * even when it or the relationship target is a component. */ +FLECS_API extern const ecs_entity_t EcsTag; + +/** Tag to indicate that relationship is stored as union. Union relationships + * enable changing the target of a union without switching tables. Union + * relationships are also marked as exclusive. */ +FLECS_API extern const ecs_entity_t EcsUnion; + +/** Tag to indicate name identifier */ +FLECS_API extern const ecs_entity_t EcsName; + +/** Tag to indicate symbol identifier */ +FLECS_API extern const ecs_entity_t EcsSymbol; + +/** Tag to indicate alias identifier */ +FLECS_API extern const ecs_entity_t EcsAlias; + +/** Used to express parent-child relationships. */ +FLECS_API extern const ecs_entity_t EcsChildOf; + +/** Used to express inheritance relationships. */ +FLECS_API extern const ecs_entity_t EcsIsA; + +/** Used to express dependency relationships */ +FLECS_API extern const ecs_entity_t EcsDependsOn; + +/** Used to express a slot (used with prefab inheritance) */ +FLECS_API extern const ecs_entity_t EcsSlotOf; + +/** Tag added to module entities */ +FLECS_API extern const ecs_entity_t EcsModule; + +/** Tag to indicate an entity/component/system is private to a module */ +FLECS_API extern const ecs_entity_t EcsPrivate; + +/** Tag added to prefab entities. Any entity with this tag is automatically + * ignored by queries, unless EcsPrefab is explicitly queried for. */ +FLECS_API extern const ecs_entity_t EcsPrefab; + +/** When this tag is added to an entity it is skipped by queries, unless + * EcsDisabled is explicitly queried for. */ +FLECS_API extern const ecs_entity_t EcsDisabled; + +/** Event that triggers when an id is added to an entity */ +FLECS_API extern const ecs_entity_t EcsOnAdd; + +/** Event that triggers when an id is removed from an entity */ +FLECS_API extern const ecs_entity_t EcsOnRemove; + +/** Event that triggers when a component is set for an entity */ +FLECS_API extern const ecs_entity_t EcsOnSet; + +/** Event that triggers when a component is unset for an entity */ +FLECS_API extern const ecs_entity_t EcsUnSet; + +/** Event that triggers observer when an entity starts/stops matching a query */ +FLECS_API extern const ecs_entity_t EcsMonitor; + +/** Event that triggers when a table is created. */ +FLECS_API extern const ecs_entity_t EcsOnTableCreate; + +/** Event that triggers when a table is deleted. */ +FLECS_API extern const ecs_entity_t EcsOnTableDelete; + +/** Event that triggers when a table becomes empty (doesn't emit on creation). */ +FLECS_API extern const ecs_entity_t EcsOnTableEmpty; + +/** Event that triggers when a table becomes non-empty. */ +FLECS_API extern const ecs_entity_t EcsOnTableFill; + +/** Relationship used for specifying cleanup behavior. */ +FLECS_API extern const ecs_entity_t EcsOnDelete; + +/** Relationship used to define what should happen when a target entity (second + * element of a pair) is deleted. */ +FLECS_API extern const ecs_entity_t EcsOnDeleteTarget; + +/** Remove cleanup policy. Must be used as target in pair with EcsOnDelete or + * EcsOnDeleteTarget. */ +FLECS_API extern const ecs_entity_t EcsRemove; + +/** Delete cleanup policy. Must be used as target in pair with EcsOnDelete or + * EcsOnDeleteTarget. */ +FLECS_API extern const ecs_entity_t EcsDelete; + +/** Panic cleanup policy. Must be used as target in pair with EcsOnDelete or + * EcsOnDeleteTarget. */ +FLECS_API extern const ecs_entity_t EcsPanic; + +/** Component that stores data for flattened relationships */ +FLECS_API extern const ecs_entity_t ecs_id(EcsTarget); + +/** Tag added to root entity to indicate its subtree should be flattened. Used + * together with assemblies. */ +FLECS_API extern const ecs_entity_t EcsFlatten; + +/** Used like (EcsDefaultChildComponent, Component). When added to an entity, + * this informs serialization formats which component to use when a value is + * assigned to an entity without specifying the component. This is intended as + * a hint, serialization formats are not required to use it. Adding this + * component does not change the behavior of core ECS operations. */ +FLECS_API extern const ecs_entity_t EcsDefaultChildComponent; + +/* Builtin predicates for comparing entity ids in queries. Only supported by rules */ +FLECS_API extern const ecs_entity_t EcsPredEq; +FLECS_API extern const ecs_entity_t EcsPredMatch; +FLECS_API extern const ecs_entity_t EcsPredLookup; + +/* Builtin marker entities for opening/closing query scopes */ +FLECS_API extern const ecs_entity_t EcsScopeOpen; +FLECS_API extern const ecs_entity_t EcsScopeClose; + +/** Tag used to indicate query is empty */ +FLECS_API extern const ecs_entity_t EcsEmpty; + +/* Pipeline module tags */ +FLECS_API extern const ecs_entity_t ecs_id(EcsPipeline); +FLECS_API extern const ecs_entity_t EcsOnStart; +FLECS_API extern const ecs_entity_t EcsPreFrame; +FLECS_API extern const ecs_entity_t EcsOnLoad; +FLECS_API extern const ecs_entity_t EcsPostLoad; +FLECS_API extern const ecs_entity_t EcsPreUpdate; +FLECS_API extern const ecs_entity_t EcsOnUpdate; +FLECS_API extern const ecs_entity_t EcsOnValidate; +FLECS_API extern const ecs_entity_t EcsPostUpdate; +FLECS_API extern const ecs_entity_t EcsPreStore; +FLECS_API extern const ecs_entity_t EcsOnStore; +FLECS_API extern const ecs_entity_t EcsPostFrame; +FLECS_API extern const ecs_entity_t EcsPhase; + +/** 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_id(EcsPoly)) + +/** The first user-defined component starts from this id. Ids up to this number + * are reserved for builtin components */ +#define EcsFirstUserComponentId (8) + +/** The first user-defined entity starts from this id. Ids up to this number + * are reserved for builtin entities */ +#define EcsFirstUserEntityId (FLECS_HI_COMPONENT_ID + 128) + +/* When visualized the reserved id ranges look like this: + * [1..8]: Builtin components + * [9..FLECS_HI_COMPONENT_ID]: Low ids reserved for application components + * [FLECS_HI_COMPONENT_ID + 1..EcsFirstUserEntityId]: Builtin entities + */ + +/** @} */ +/** @} */ + +/** + * @defgroup world_api World + * Functions for working with `ecs_world_t`. + * + * @{ + */ + +/** + * @defgroup world_creation_deletion Creation & Deletion + * @{ + */ + +/** Create a new world. + * This operation automatically imports modules from addons Flecs has been built + * with, except when the module specifies otherwise. + * + * @return A new world + */ +FLECS_API +ecs_world_t* ecs_init(void); + +/** Create a new world with just the core module. + * Same as ecs_init(), but doesn't import modules from addons. This operation is + * faster than ecs_init() and results in less memory utilization. + * + * @return A new tiny world + */ +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. Command line + * arguments are used to: + * - automatically derive the name of the application from argv[0] + * + * @return A new world + */ +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); + +/** Returns whether the world is being deleted. + * This operation can be used in callbacks like type hooks or observers to + * detect if they are invoked while the world is being deleted. + * + * @param world The world. + * @return True if being deleted, false if not. + */ +FLECS_API +bool ecs_is_fini( + const 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); + +/** @} */ + +/** + * @defgroup world_frame Frame functions + * @{ + */ + +/** Begin frame. + * When an application does not use ecs_progress() to control the main loop, it + * can still use Flecs features such as FPS limiting and time measurements. This + * operation needs to be invoked whenever a new frame is about to get processed. + * + * Calls to ecs_frame_begin() must always be followed by ecs_frame_end(). + * + * The function accepts a delta_time parameter, which will get passed to + * systems. This value is also used to compute the amount of time the function + * needs to sleep to ensure it does not exceed the target_fps, when it is set. + * When 0 is provided for delta_time, the time will be measured. + * + * This function should only be ran from the main thread. + * + * @param world The world. + * @param delta_time Time elapsed since the last frame. + * @return The provided delta_time, or measured time if 0 was provided. + */ +FLECS_API +ecs_ftime_t ecs_frame_begin( + ecs_world_t *world, + ecs_ftime_t delta_time); + +/** End frame. + * This operation must be called at the end of the frame, and always after + * ecs_frame_begin(). + * + * @param world The world. + */ +FLECS_API +void ecs_frame_end( + ecs_world_t *world); + +/** 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); + +/** 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); + +/** Return whether a quit has been signaled. + * + * @param world The world. + */ +FLECS_API +bool ecs_should_quit( + const ecs_world_t *world); + +/** 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); + +/** 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, + ecs_ftime_t fps); + +/** @} */ + +/** + * @defgroup commands Commands + * @{ + */ + +/** Begin readonly mode. + * Readonly mode guarantees that no mutations will occur on the world, which + * makes the world safe to access from multiple threads. While the world is in + * readonly mode, operations are deferred. + * + * Note that while similar to ecs_defer_begin(), deferring only does not guarantee + * the world is not mutated. Operations that are not deferred (like creating a + * query) update data structures on the world and are allowed when deferring is + * enabled, but not when the world is in readonly mode. + * + * A call to ecs_readonly_begin() must be followed up with ecs_readonly_end(). + * + * The ecs_progress() function automatically enables readonly mode while systems + * are executed. + * + * When a world has more than one stage, the specific stage must be provided to + * mutating ECS operations. Failing to do so will throw a readonly assert. A + * world typically has more than one stage when using threads. An example: + * + * @code + * ecs_set_stage_count(world, 2); + * ecs_stage_t *stage = ecs_get_stage(world, 1); + * + * ecs_readonly_begin(world); + * ecs_add(world, e, Tag); // readonly assert + * ecs_add(stage, e, Tag); // OK + * @endcode + * + * @param world The world + * @return Whether world is in readonly mode. + */ +FLECS_API +bool ecs_readonly_begin( + ecs_world_t *world); + +/** End readonly mode. + * This operation ends readonly mode, and must be called after + * ecs_readonly_begin(). Operations that were deferred while the world was in + * readonly mode will be flushed. + * + * @param world The world + */ +FLECS_API +void ecs_readonly_end( + ecs_world_t *world); + +/** Merge world or stage. + * When automatic merging is disabled, an application can call this + * operation on either an individual stage, or on the world which will merge + * all stages. This operation may only be called when staging is not enabled + * (either after ecs_progress() or after ecs_readonly_end()). + * + * This operation may be called on an already merged stage or world. + * + * @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 + * ecs_defer_begin() and ecs_defer_end() operations are executed at the end + * of the frame. + * + * This operation is thread safe. + * + * @param world The world. + * @return true if world changed from non-deferred mode to deferred mode. + */ +FLECS_API +bool ecs_defer_begin( + ecs_world_t *world); + +/** Test if deferring is enabled for current stage. + * + * @param world The world. + * @return True if deferred, false if not. + */ +FLECS_API +bool ecs_is_deferred( + const ecs_world_t *world); + +/** End block of operations to defer. + * See ecs_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); + +/** Suspend deferring but do not flush queue. + * This operation can be used to do an undeferred operation while not flushing + * the operations in the queue. + * + * An application should invoke ecs_defer_resume() before ecs_defer_end() is called. + * The operation may only be called when deferring is enabled. + * + * @param world The world. + */ +FLECS_API +void ecs_defer_suspend( + ecs_world_t *world); + +/** Resume deferring. + * See ecs_defer_suspend(). + * + * @param world The world. + */ +FLECS_API +void ecs_defer_resume( + ecs_world_t *world); + +/** Enable/disable auto-merging for world or stage. + * When auto-merging is enabled, staged data will automatically be merged with + * the world when staging ends. This happens at the end of ecs_progress(), at a + * sync point or when ecs_readonly_end() is called. + * + * Applications can exercise more control over when data from a stage is merged + * by disabling auto-merging. This requires an application to explicitly call + * ecs_merge() on the stage. + * + * When this function is invoked on the world, it sets all current stages to + * the provided value and sets the default for new stages. When this function is + * invoked on a stage, auto-merging is only set for that specific stage. + * + * @param world The world. + * @param automerge Whether to enable or disable auto-merging. + */ +FLECS_API +void ecs_set_automerge( + ecs_world_t *world, + bool automerge); + +/** Configure world to have N stages. + * This initializes N stages, which allows applications to defer operations to + * multiple isolated defer queues. This is typically used for applications with + * multiple threads, where each thread gets its own queue, and commands are + * merged when threads are synchronized. + * + * Note that the ecs_set_threads() function already creates the appropriate + * number of stages. The ecs_set_stage_count() operation is useful for applications + * that want to manage their own stages and/or threads. + * + * @param world The world. + * @param stages The number of stages. + */ +FLECS_API +void ecs_set_stage_count( + ecs_world_t *world, + int32_t stages); + +/** Get number of configured stages. + * Return number of stages set by ecs_set_stage_count(). + * + * @param world The world. + * @return The number of stages used for threading. + */ +FLECS_API +int32_t ecs_get_stage_count( + const ecs_world_t *world); + +/** Get current stage id. + * The stage id can be used by an application to learn about which stage it is + * using, which typically corresponds with the worker thread id. + * + * @param world The world. + * @return The stage id. + */ +FLECS_API +int32_t ecs_get_stage_id( + const ecs_world_t *world); + +/** Get stage-specific world pointer. + * Flecs threads can safely invoke the API as long as they have a private + * context to write to, also referred to as the stage. This function returns a + * pointer to a stage, disguised as a world pointer. + * + * Note that this function does not(!) create a new world. It simply wraps the + * existing world in a thread-specific context, which the API knows how to + * unwrap. The reason the stage is returned as an ecs_world_t is so that it + * can be passed transparently to the existing API functions, vs. having to + * create a dedicated API for threading. + * + * @param world The world. + * @param stage_id The index of the stage to retrieve. + * @return A thread-specific pointer to the world. + */ +FLECS_API +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id); + +/** Test whether the current world is readonly. + * This function allows the code to test whether the currently used world + * is readonly or whether it allows for writing. + * + * @param world A pointer to a stage or the world. + * @return True if the world or stage is readonly. + */ +FLECS_API +bool ecs_stage_is_readonly( + const ecs_world_t *world); + +/** Create asynchronous stage. + * An asynchronous stage can be used to asynchronously queue operations for + * later merging with the world. An asynchronous stage is similar to a regular + * stage, except that it does not allow reading from the world. + * + * Asynchronous stages are never merged automatically, and must therefore be + * manually merged with the ecs_merge() function. It is not necessary to call + * ecs_defer_begin() or ecs_defer_end() before and after enqueuing commands, as an + * asynchronous stage unconditionally defers operations. + * + * The application must ensure that no commands are added to the stage while the + * stage is being merged. + * + * An asynchronous stage must be cleaned up by ecs_async_stage_free(). + * + * @param world The world. + * @return The stage. + */ +FLECS_API +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world); + +/** Free asynchronous stage. + * The provided stage must be an asynchronous stage. If a non-asynchronous stage + * is provided, the operation will fail. + * + * @param stage The stage to free. + */ +FLECS_API +void ecs_async_stage_free( + ecs_world_t *stage); + +/** Test whether provided stage is asynchronous. + * + * @param stage The stage. + * @return True when the stage is asynchronous, false for a regular stage or + * world. + */ +FLECS_API +bool ecs_stage_is_async( + ecs_world_t *stage); + +/** @} */ + +/** + * @defgroup world_misc Misc + * @{ + */ + +/** 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. + * + * @param world The world. + * @param ctx A pointer to a user defined structure. + * @param ctx_free A function that is invoked with ctx when the world is freed. + */ +FLECS_API +void ecs_set_ctx( + ecs_world_t *world, + void *ctx, + ecs_ctx_free_t ctx_free); + +/** Set a world binding context. + * Same as ecs_set_ctx() but for binding context. A binding context is intended + * specifically for language bindings to store binding specific data. + * + * @param world The world. + * @param ctx A pointer to a user defined structure. + * @param ctx_free A function that is invoked with ctx when the world is freed. + */ +FLECS_API +void ecs_set_binding_ctx( + ecs_world_t *world, + void *ctx, + ecs_ctx_free_t ctx_free); + +/** Get the world context. + * This operation retrieves a previously set world context. + * + * @param world The world. + * @return The context set with ecs_set_ctx(). If no context was set, the + * function returns NULL. + */ +FLECS_API +void* ecs_get_ctx( + const ecs_world_t *world); + +/** Get the world binding context. + * This operation retrieves a previously set world binding context. + * + * @param world The world. + * @return The context set with ecs_set_binding_ctx(). If no context was set, the + * function returns NULL. + */ +FLECS_API +void* ecs_get_binding_ctx( + const ecs_world_t *world); + +/** Get world info. + * + * @param world The world. + * @return Pointer to the world info. Valid for as long as the world exists. + */ +FLECS_API +const ecs_world_info_t* ecs_get_world_info( + const 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. + * + * @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); + +/** Set a range for issuing 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 issuing 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); + +/** Get the largest issued entity id (not counting generation). + * + * @param world The world. + */ +FLECS_API +ecs_entity_t ecs_get_max_id( + const ecs_world_t *world); + +/** Force aperiodic actions. + * The world may delay certain operations until they are necessary for the + * application to function correctly. This may cause observable side effects + * such as delayed triggering of events, which can be inconvenient when for + * example running a test suite. + * + * The flags parameter specifies which aperiodic actions to run. Specify 0 to + * run all actions. Supported flags start with 'EcsAperiodic'. Flags identify + * internal mechanisms and may change unannounced. + * + * @param world The world. + * @param flags The flags specifying which actions to run. + */ +FLECS_API +void ecs_run_aperiodic( + ecs_world_t *world, + ecs_flags32_t flags); + +/** Cleanup empty tables. + * This operation cleans up empty tables that meet certain conditions. Having + * large amounts of empty tables does not negatively impact performance of the + * ECS, but can take up considerable amounts of memory, especially in + * applications with many components, and many components per entity. + * + * The generation specifies the minimum number of times this operation has + * to be called before an empty table is cleaned up. If a table becomes non + * empty, the generation is reset. + * + * The operation allows for both a "clear" generation and a "delete" + * generation. When the clear generation is reached, the table's + * resources are freed (like component arrays) but the table itself is not + * deleted. When the delete generation is reached, the empty table is deleted. + * + * By specifying a non-zero id the cleanup logic can be limited to tables with + * a specific (component) id. The operation will only increase the generation + * count of matching tables. + * + * The min_id_count specifies a lower bound for the number of components a table + * should have. Often the more components a table has, the more specific it is + * and therefore less likely to be reused. + * + * The time budget specifies how long the operation should take at most. + * + * @param world The world. + * @param id Optional component filter for the tables to evaluate. + * @param clear_generation Free table data when generation > clear_generation. + * @param delete_generation Delete table when generation > delete_generation. + * @param min_id_count Minimum number of component ids the table should have. + * @param time_budget_seconds Amount of time operation is allowed to spend. + * @return Number of deleted tables. + */ +FLECS_API +int32_t ecs_delete_empty_tables( + ecs_world_t *world, + ecs_id_t id, + uint16_t clear_generation, + uint16_t delete_generation, + int32_t min_id_count, + double time_budget_seconds); + +/** Get world from poly. + * + * @param poly A pointer to a poly object. + * @return The world. + */ +FLECS_API +const ecs_world_t* ecs_get_world( + const ecs_poly_t *poly); + +/** Get entity from poly. + * + * @param poly A pointer to a poly object. + * @return Entity associated with the poly object. + */ +FLECS_API +ecs_entity_t ecs_get_entity( + const ecs_poly_t *poly); + +/** Test if pointer is of specified type. + * Usage: + * + * @code + * ecs_poly_is(ptr, ecs_world_t) + * @endcode + * + * This operation only works for poly types. + * + * @param object The object to test. + * @param type The id of the type. + * @return True if the pointer is of the specified type. + */ +FLECS_API +bool ecs_poly_is_( + const ecs_poly_t *object, + int32_t type); + +#define ecs_poly_is(object, type)\ + ecs_poly_is_(object, type##_magic) + +/** Make a pair id. + * This function is equivalent to using the ecs_pair() macro, and is added for + * convenience to make it easier for non C/C++ bindings to work with pairs. + * + * @param first The first element of the pair of the pair. + * @param second The target of the pair. + */ +FLECS_API +ecs_id_t ecs_make_pair( + ecs_entity_t first, + ecs_entity_t second); + +/** @} */ + +/** @} */ + +/** + * @defgroup entities Entities + * Functions for working with `ecs_entity_t`. + * + * @{ + */ + +/** + * @defgroup creating_entities Creating & Deleting + * Functions for creating and deleting entities. + * + * @{ + */ + +/** Create new entity id. + * This operation returns an unused entity id. This operation is guaranteed to + * return an empty entity as it does not use values set by ecs_set_scope() or + * ecs_set_with(). + * + * @param world The world. + * @return The new entity id. + */ +FLECS_API +ecs_entity_t ecs_new_id( + ecs_world_t *world); + +/** Create new low id. + * This operation returns a new low id. Entity ids start after the + * FLECS_HI_COMPONENT_ID constant. This reserves a range of low ids for things + * like components, and allows parts of the code to optimize operations. + * + * Note that FLECS_HI_COMPONENT_ID does not represent the maximum number of + * components that can be created, only the maximum number of components that + * can take advantage of these optimizations. + * + * This operation is guaranteed to return an empty entity as it does not use + * values set by ecs_set_scope() or ecs_set_with(). + * + * This operation does not recycle ids. + * + * @param world The world. + * @return The new component id. + */ +FLECS_API +ecs_entity_t ecs_new_low_id( + ecs_world_t *world); + +/** Create new entity with (component) id. + * This operation creates a new entity with an optional (component) id. When 0 + * is passed to the id parameter, no component is added to the new entity. + * + * @param world The world. + * @param id The component id to initialize the new entity with. + * @return The new entity. + */ +FLECS_API +ecs_entity_t ecs_new_w_id( + ecs_world_t *world, + ecs_id_t id); + +/** Create new entity in table. + * This operation creates a new entity in the specified table. + * + * @param world The world. + * @param table The table to which to add the new entity. + * @return The new entity. + */ +FLECS_API +ecs_entity_t ecs_new_w_table( + ecs_world_t *world, + ecs_table_t *table); + +/** Find or create an entity. + * This operation creates a new entity, or modifies an existing one. When a name + * is set in the ecs_entity_desc_t::name field and ecs_entity_desc_t::entity is + * not set, the operation will first attempt to find an existing entity by that + * name. If no entity with that name can be found, it will be created. + * + * If both a name and entity handle are provided, the operation will check if + * the entity name matches with the provided name. If the names do not match, + * the function will fail and return 0. + * + * If an id to a non-existing entity is provided, that entity id become alive. + * + * See the documentation of ecs_entity_desc_t for more details. + * + * @param world The world. + * @param desc Entity init parameters. + * @return A handle to the new or existing entity, or 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_entity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc); + +/** Bulk create/populate new entities. + * This operation bulk inserts a list of new or predefined entities into a + * single table. + * + * The operation does not take ownership of component arrays provided by the + * application. Components that are non-trivially copyable will be moved into + * the storage. + * + * The operation will emit OnAdd events for each added id, and OnSet events for + * each component that has been set. + * + * If no entity ids are provided by the application, the returned array of ids + * points to an internal data structure which changes when new entities are + * created/deleted. + * + * If as a result of the operation triggers are invoked that deletes + * entities and no entity ids were provided by the application, the returned + * array of identifiers may be incorrect. To avoid this problem, an application + * can first call ecs_bulk_init() to create empty entities, copy the array to one + * that is owned by the application, and then use this array to populate the + * entities. + * + * @param world The world. + * @param desc Bulk creation parameters. + * @return Array with the list of entity ids created/populated. + */ +FLECS_API +const ecs_entity_t* ecs_bulk_init( + ecs_world_t *world, + const ecs_bulk_desc_t *desc); + +/** Create N new entities. + * This operation is the same as ecs_new_w_id(), but creates N entities + * instead of one. + * + * @param world The world. + * @param id The component id to create the entities with. + * @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_id( + ecs_world_t *world, + ecs_id_t id, + int32_t 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. + * + * If the source entity has a name, it will not be copied to the destination + * entity. This is to prevent having two entities with the same name under the + * same parent, which is not allowed. + * + * @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); + +/** Delete an entity. + * This operation will delete an entity and all of its components. The entity id + * will be made available for recycling. If the entity passed to ecs_delete() is + * not alive, the operation will have no side effects. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity); + +/** Delete all entities with the specified id. + * This will delete all entities (tables) that have the specified id. The id + * may be a wildcard and/or a pair. + * + * @param world The world. + * @param id The id. + */ +FLECS_API +void ecs_delete_with( + ecs_world_t *world, + ecs_id_t id); + +/** @} */ + +/** + * @defgroup adding_removing Adding & Removing + * Functions for adding and removing components. + * + * @{ + */ + +/** Add a (component) id to an entity. + * This operation adds a single (component) id to an entity. If the entity + * already has the id, this operation will have no side effects. + * + * @param world The world. + * @param entity The entity. + * @param id The id to add. + */ +FLECS_API +void ecs_add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Remove a (component) id from an entity. + * This operation removes a single (component) id to an entity. If the entity + * does not have the id, this operation will have no side effects. + * + * @param world The world. + * @param entity The entity. + * @param id The id to remove. + */ +FLECS_API +void ecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Add override for (component) id. + * Adding an override to an entity ensures that when the entity is instantiated + * (by adding an IsA relationship to it) the component with the override is + * copied to a component that is private to the instance. By default components + * reachable through an IsA relationship are shared. + * + * Adding an override does not add the component. If an override is added to an + * entity that does not have the component, it will still be added to the + * instance, but with an uninitialized value (unless the component has a ctor). + * When the entity does have the entity, the component of the instance will be + * initialized with the value of the component on the entity. + * + * This is the same as what happens when calling ecs_add_id() for an id that is + * inherited (reachable through an IsA relationship). + * + * This operation is equivalent to doing: + * + * @code + * ecs_add_id(world, entity, ECS_OVERRIDE | id); + * @endcode + * + * @param world The world. + * @param entity The entity. + * @param id The id to override. + */ +FLECS_API +void ecs_override_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Clear all components. + * This operation will remove all components from an entity. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity); + +/** Remove all instances of the specified (component) id. + * This will remove the specified id from all entities (tables). The id may be + * a wildcard and/or a pair. + * + * @param world The world. + * @param id The id. + */ +FLECS_API +void ecs_remove_all( + ecs_world_t *world, + ecs_id_t id); + +/** Set current with id. + * New entities are automatically created with the specified id. + * + * @param world The world. + * @param id The id. + * @return The previous id. + */ +FLECS_API +ecs_entity_t ecs_set_with( + ecs_world_t *world, + ecs_id_t id); + +/** Get current with id. + * Get the id set with ecs_set_with(). + * + * @param world The world. + * @return The last id provided to ecs_set_with(). + */ +FLECS_API +ecs_id_t ecs_get_with( + const ecs_world_t *world); + +/** @} */ + +/** + * @defgroup enabling_disabling Enabling & Disabling + * Functions for enabling/disabling entities and components. + * + * @{ + */ + +/** Enable or disable 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); + +/** 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 id The component. + * @param enable True to enable the component, false to disable. + */ +FLECS_API +void ecs_enable_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool 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 id The component. + * @return True if the component is enabled, otherwise false. + */ +FLECS_API +bool ecs_is_enabled_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** @} */ + +/** + * @defgroup getting Getting & Setting + * Functions for getting/setting 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 id The id of the component to get. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +const void* ecs_get_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Create a component ref. + * A ref is a handle to an entity + component which caches a small amount of + * data to reduce overhead of repeatedly accessing the component. Use + * ecs_ref_get() to get the component data. + * + * @param world The world. + * @param entity The entity. + * @param id The id of the component. + * @return The reference. + */ +FLECS_API +ecs_ref_t ecs_ref_init_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Get component from ref. + * Get component pointer from ref. The ref must be created with ecs_ref_init(). + * + * @param world The world. + * @param ref The ref. + * @param id The component id. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +void* ecs_ref_get_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_id_t id); + +/** Update ref. + * Ensures contents of ref are up to date. Same as ecs_ref_get_id(), but does not + * return pointer to component id. + * + * @param world The world. + * @param ref The ref. + */ +FLECS_API +void ecs_ref_update( + const ecs_world_t *world, + ecs_ref_t *ref); + +/** Get a mutable pointer to a component. + * This operation returns a mutable pointer to a component. If the component did + * not yet exist, it will be added. + * + * If get_mut is called when the world is in deferred/readonly mode, the + * function will: + * - return a pointer to a temp storage if the component does not yet exist, or + * - return a pointer to the existing component if it exists + * + * @param world The world. + * @param entity The entity. + * @param id The entity id of the component to obtain. + * @return The component pointer. + */ +FLECS_API +void* ecs_get_mut_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Combines get_mut + modified in single operation. + * This operation is a more efficient alternative to calling ecs_get_mut_id() and + * ecs_modified_id() separately. This operation is only valid when the world is in + * deferred mode, which ensures that the Modified event is not emitted before + * the modification takes place. + * + * @param world The world. + * @param entity The entity. + * @param id The id of the component to obtain. + * @return The component pointer. + */ +FLECS_API +void* ecs_get_mut_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Begin exclusive write access to entity. + * This operation provides safe exclusive access to the components of an entity + * without the overhead of deferring operations. + * + * When this operation is called simultaneously for the same entity more than + * once it will throw an assert. Note that for this to happen, asserts must be + * enabled. It is up to the application to ensure that access is exclusive, for + * example by using a read-write mutex. + * + * Exclusive access is enforced at the table level, so only one entity can be + * exclusively accessed per table. The exclusive access check is thread safe. + * + * This operation must be followed up with ecs_write_end(). + * + * @param world The world. + * @param entity The entity. + * @return A record to the entity. + */ +FLECS_API +ecs_record_t* ecs_write_begin( + ecs_world_t *world, + ecs_entity_t entity); + +/** End exclusive write access to entity. + * This operation ends exclusive access, and must be called after + * ecs_write_begin(). + * + * @param record Record to the entity. + */ +FLECS_API +void ecs_write_end( + ecs_record_t *record); + +/** Begin read access to entity. + * This operation provides safe read access to the components of an entity. + * Multiple simultaneous reads are allowed per entity. + * + * This operation ensures that code attempting to mutate the entity's table will + * throw an assert. Note that for this to happen, asserts must be enabled. It is + * up to the application to ensure that this does not happen, for example by + * using a read-write mutex. + * + * This operation does *not* provide the same guarantees as a read-write mutex, + * as it is possible to call ecs_read_begin() after calling ecs_write_begin(). It is + * up to application has to ensure that this does not happen. + * + * This operation must be followed up with ecs_read_end(). + * + * @param world The world. + * @param entity The entity. + * @return A record to the entity. + */ +FLECS_API +const ecs_record_t* ecs_read_begin( + ecs_world_t *world, + ecs_entity_t entity); + +/** End read access to entity. + * This operation ends read access, and must be called after ecs_read_begin(). + * + * @param record Record to the entity. + */ +FLECS_API +void ecs_read_end( + const ecs_record_t *record); + +/** Get entity corresponding with record. + * This operation only works for entities that are not empty. + * + * @param record The record for which to obtain the entity id. + */ +FLECS_API +ecs_entity_t ecs_record_get_entity( + const ecs_record_t *record); + +/** Get component from entity record. + * This operation returns a pointer to a component for the entity + * associated with the provided record. For safe access to the component, obtain + * the record with ecs_read_begin() or ecs_write_begin(). + * + * Obtaining a component from a record is faster than obtaining it from the + * entity handle, as it reduces the number of lookups required. + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Pointer to component, or NULL if entity does not have the component. + */ +FLECS_API +const void* ecs_record_get_id( + const ecs_world_t *world, + const ecs_record_t *record, + ecs_id_t id); + +/** Same as ecs_record_get_id(), but returns a mutable pointer. + * For safe access to the component, obtain the record with ecs_write_begin(). + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Pointer to component, or NULL if entity does not have the component. + */ +FLECS_API +void* ecs_record_get_mut_id( + ecs_world_t *world, + ecs_record_t *record, + ecs_id_t id); + +/** Test if entity for record has component. + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + */ +FLECS_API +bool ecs_record_has_id( + ecs_world_t *world, + const ecs_record_t *record, + ecs_id_t id); + +/** Emplace a component. + * Emplace is similar to ecs_get_mut_id() except that the component constructor is not + * invoked for the returned pointer, allowing the component to be "constructed" + * directly in the storage. + * + * Emplace can only be used if the entity does not yet have the component. If + * the entity has the component, the operation will fail. + * + * @param world The world. + * @param entity The entity. + * @param id The component to obtain. + * @return The (uninitialized) component pointer. + */ +FLECS_API +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Signal that a component has been modified. + * This operation is usually used after modifying a component value obtained by + * ecs_get_mut_id(). The operation will mark the component as dirty, and invoke + * OnSet observers and hooks. + * + * @param world The world. + * @param entity The entity. + * @param id The id of the component that was modified. + */ +FLECS_API +void ecs_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** 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_id() followed by + * ecs_modified_id(). The operation will not modify the value of the passed in + * component. If the component has a copy hook registered, it will be used to + * copy in the component. + * + * If the provided entity is 0, a new entity will be created. + * + * @param world The world. + * @param entity The entity. + * @param id The id of the component to set. + * @param size The size of the pointed-to 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_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr); + +/** @} */ + +/** + * @defgroup liveliness Entity Liveliness + * Functions for testing and modifying entity liveliness. + * + * @{ + */ + +/** Test whether an entity is valid. + * Entities that are valid can be used with API functions. Using invalid + * entities with API operations will cause the function to panic. + * + * An entity is valid if it is not 0 and if it is alive. + * + * ecs_is_valid() will return true for ids that don't exist (alive or not alive). This + * allows for using ids that have never been created by ecs_new() or similar. In + * this the function differs from ecs_is_alive(), which will return false for + * entities that do not yet exist. + * + * The operation will return false for an id that exists and is not alive, as + * using this id with an API operation would cause it to assert. + * + * @param world The world. + * @param e The entity. + * @return True if the entity is valid, false if the entity is not valid. + */ +FLECS_API +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t e); + +/** Test whether an entity is alive. + * Entities are alive after they are created, and become not alive when they are + * deleted. Operations that return alive ids are (amongst others) ecs_new_id(), + * ecs_new_low_id() and ecs_entity_init(). Ids can be made alive with the ecs_ensure() + * function. + * + * After an id is deleted it can be recycled. Recycled ids are different from + * the original id in that they have a different generation count. This makes it + * possible for the API to distinguish between the two. An example: + * + * @code + * ecs_entity_t e1 = ecs_new_id(world); + * ecs_is_alive(world, e1); // true + * ecs_delete(world, e1); + * ecs_is_alive(world, e1); // false + * + * ecs_entity_t e2 = ecs_new_id(world); // recycles e1 + * ecs_is_alive(world, e2); // true + * ecs_is_alive(world, e1); // false + * @endcode + * + * @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( + const ecs_world_t *world, + ecs_entity_t e); + +/** Remove generation from entity id. + * + * @param e The entity id. + * @return The entity id without the generation count. + */ +FLECS_API +ecs_id_t ecs_strip_generation( + ecs_entity_t e); + +/** Override the generation of an entity. + * The generation count of an entity is increased each time an entity is deleted + * and is used to test whether an entity id is alive. + * + * This operation overrides the current generation of an entity with the + * specified generation, which can be useful if an entity is externally managed, + * like for external pools, savefiles or netcode. + * + * @param world The world. + * @param entity Entity for which to set the generation with the new generation. + */ +FLECS_API +void ecs_set_entity_generation( + ecs_world_t *world, + ecs_entity_t entity); + +/** Get alive identifier. + * In some cases an application may need to work with identifiers from which + * the generation has been stripped. A typical scenario in which this happens is + * when iterating relationships in an entity type. + * + * For example, when obtaining the parent id from a ChildOf relationship, the parent + * (second element of the pair) will have been stored in a 32 bit value, which + * cannot store the entity generation. This function can retrieve the identifier + * with the current generation for that id. + * + * If the provided identifier is not alive, the function will return 0. + * + * @param world The world. + * @param e The for which to obtain the current alive entity id. + * @return The alive entity id if there is one, or 0 if the id is not alive. + */ +FLECS_API +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t e); + +/** Ensure id is alive. + * This operation ensures that the provided id is alive. This is useful in + * scenarios where an application has an existing id that has not been created + * with ecs_new() (such as a global constant or an id from a remote application). + * + * When this operation is successful it guarantees that the provided id exists, + * is valid and is alive. + * + * Before this operation the id must either not be alive or have a generation + * that is equal to the passed in entity. + * + * If the provided id has a non-zero generation count and the id does not exist + * in the world, the id will be created with the specified generation. + * + * If the provided id is alive and has a generation count that does not match + * the provided id, the operation will fail. + * + * @param world The world. + * @param entity The entity id to make alive. + */ +FLECS_API +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t entity); + +/** Same as ecs_ensure(), but for (component) ids. + * An id can be an entity or pair, and can contain id flags. This operation + * ensures that the entity (or entities, for a pair) are alive. + * + * When this operation is successful it guarantees that the provided id can be + * used in operations that accept an id. + * + * Since entities in a pair do not encode their generation ids, this operation + * will not fail when an entity with non-zero generation count already exists in + * the world. + * + * This is different from ecs_ensure(), which will fail if attempted with an id + * that has generation 0 and an entity with a non-zero generation is currently + * alive. + * + * @param world The world. + * @param id The id to make alive. + */ +FLECS_API +void ecs_ensure_id( + ecs_world_t *world, + ecs_id_t id); + +/** Test whether an entity exists. + * Similar as ecs_is_alive(), but ignores entity generation count. + * + * @param world The world. + * @param entity The entity. + * @return True if the entity exists, false if the entity does not exist. + */ +FLECS_API +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity); + +/** @} */ + +/** + * @defgroup entity_info Entity Information. + * Get information from entity. + * + * @{ + */ + +/** 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 +const ecs_type_t* ecs_get_type( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get the table of an entity. + * + * @param world The world. + * @param entity The entity. + * @return The table of the entity, NULL if the entity has no components/tags. + */ +FLECS_API +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Convert type to string. + * The result of this operation must be freed with ecs_os_free(). + * + * @param world The world. + * @param type The type. + * @return The stringified type. + */ +FLECS_API +char* ecs_type_str( + const ecs_world_t *world, + const ecs_type_t* type); + +/** Convert table to string. + * Same as ecs_type_str(world, ecs_table_get_type(table)). The result of this + * operation must be freed with ecs_os_free(). + * + * @param world The world. + * @param table The table. + * @return The stringified table type. + */ +FLECS_API +char* ecs_table_str( + const ecs_world_t *world, + const ecs_table_t *table); + +/** Convert entity to string. + * Same as combining: + * - ecs_get_fullpath(world, entity) + * - ecs_type_str(world, ecs_get_type(world, entity)) + * + * The result of this operation must be freed with ecs_os_free(). + * + * @param world The world. + * @param entity The entity. + * @return The entity path with stringified type. + */ +FLECS_API +char* ecs_entity_str( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Test if an entity has an id. + * This operation returns true if the entity has or inherits the specified id. + * + * @param world The world. + * @param entity The entity. + * @param id The id to test for. + * @return True if the entity has the id, false if not. + */ +FLECS_API +bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Test if an entity owns an id. + * This operation returns true if the entity has the specified id. The operation + * behaves the same as ecs_has_id(), except that it will return false for + * components that are inherited through an IsA relationship. + * + * @param world The world. + * @param entity The entity. + * @param id The id to test for. + * @return True if the entity has the id, false if not. + */ +FLECS_API +bool ecs_owns_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Get the target of a relationship. + * This will return a target (second element of a pair) of the entity for the + * specified relationship. The index allows for iterating through the targets, + * if a single entity has multiple targets for the same relationship. + * + * If the index is larger than the total number of instances the entity has for + * the relationship, the operation will return 0. + * + * @param world The world. + * @param entity The entity. + * @param rel The relationship between the entity and the target. + * @param index The index of the relationship instance. + * @return The target for the relationship at the specified index. + */ +FLECS_API +ecs_entity_t ecs_get_target( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index); + +/** Get parent (target of ChildOf relationship) for entity. + * This operation is the same as calling: + * + * @code + * ecs_get_target(world, entity, EcsChildOf, 0); + * @endcode + * + * @param world The world. + * @param entity The entity. + * @return The parent of the entity, 0 if the entity has no parent. + */ +FLECS_API +ecs_entity_t ecs_get_parent( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get the target of a relationship for a given id. + * This operation returns the first entity that has the provided id by following + * the specified relationship. If the entity itself has the id then entity will + * be returned. If the id cannot be found on the entity or by following the + * relationship, the operation will return 0. + * + * This operation can be used to lookup, for example, which prefab is providing + * a component by specifying the IsA relationship: + * + * @code + * // Is Position provided by the entity or one of its base entities? + * ecs_get_target_for_id(world, entity, EcsIsA, ecs_id(Position)) + * @endcode + * + * @param world The world. + * @param entity The entity. + * @param rel The relationship to follow. + * @param id The id to lookup. + * @return The entity for which the target has been found. + */ +FLECS_API +ecs_entity_t ecs_get_target_for_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + ecs_id_t id); + +/** Return depth for entity in tree for the specified relationship. + * Depth is determined by counting the number of targets encountered while + * traversing up the relationship tree for rel. Only acyclic relationships are + * supported. + * + * @param world The world. + * @param entity The entity. + * @param rel The relationship. + * @return The depth of the entity in the tree. + */ +FLECS_API +int32_t ecs_get_depth( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel); + +typedef struct ecs_flatten_desc_t { + /* When true, the flatten operation will not remove names from entities in + * the flattened tree. This may fail if entities from different subtrees + * have the same name. */ + bool keep_names; + + /* When true, the flattened tree won't contain information about the + * original depth of the entities. This can reduce fragmentation, but may + * cause existing code, such as cascade queries, to no longer work. */ + bool lose_depth; +} ecs_flatten_desc_t; + +/** Recursively flatten relationship for target entity (experimental). + * This operation combines entities in the subtree of the specified pair from + * different parents in the same table. This can reduce memory fragmentation + * and reduces the number of tables in the storage, which improves RAM + * utilization and various other operations, such as entity cleanup. + * + * The lifecycle of entities in a fixed subtree are bound to the specified + * parent. Entities in a fixed subtree cannot be deleted individually. Entities + * can also not change the target of the fixed relationship, which includes + * removing the relationship. + * + * Entities in a fixed subtree are still fragmented on subtree depth. This + * ensures that entities can still be iterated in breadth-first order with the + * cascade query modifier. + * + * The current implementation is limited to exclusive acyclic relationships, and + * does not allow for adding/removing to entities in flattened tables. An entity + * may only be flattened for a single relationship. Future iterations of the + * feature may remove these limitations. + * + * @param world The world. + * @param pair The relationship pair from which to start flattening. + * @param desc Options for flattening the tree. + */ +FLECS_API +void ecs_flatten( + ecs_world_t *world, + ecs_id_t pair, + const ecs_flatten_desc_t *desc); + +/** Count entities that have the specified id. + * Returns the number of entities that have the specified id. + * + * @param world The world. + * @param entity The id to search for. + * @return The number of entities that have the id. + */ +FLECS_API +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_id_t entity); + +/** @} */ + + +/** + * @defgroup paths Entity Names + * Functions for working with entity names and paths. + * + * @{ + */ + +/** Get the name of an entity. + * This will return the name stored in (EcsIdentifier, EcsName). + * + * @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( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get the symbol of an entity. + * This will return the symbol stored in (EcsIdentifier, EcsSymbol). + * + * @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_symbol( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Set the name of an entity. + * This will set or overwrite the name of an entity. If no entity is provided, + * a new entity will be created. + * + * The name is stored in (EcsIdentifier, EcsName). + * + * @param world The world. + * @param entity The entity. + * @param name The name. + * @return The provided entity, or a new entity if 0 was provided. + */ +FLECS_API +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +/** Set the symbol of an entity. + * This will set or overwrite the symbol of an entity. If no entity is provided, + * a new entity will be created. + * + * The symbol is stored in (EcsIdentifier, EcsSymbol). + * + * @param world The world. + * @param entity The entity. + * @param symbol The symbol. + * @return The provided entity, or a new entity if 0 was provided. + */ +FLECS_API +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *symbol); + +/** Set alias for entity. + * An entity can be looked up using its alias from the root scope without + * providing the fully qualified name if its parent. An entity can only have + * a single alias. + * + * The symbol is stored in (EcsIdentifier, EcsAlias). + * + * @param world The world. + * @param entity The entity. + * @param alias The alias. + */ +FLECS_API +void ecs_set_alias( + ecs_world_t *world, + ecs_entity_t entity, + const char *alias); + +/** 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( + const 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( + const 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. + * @param recursive Recursively traverse up the tree until entity is found. + * @return The entity if found, else 0. + */ +FLECS_API +ecs_entity_t ecs_lookup_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix, + bool recursive); + +/** Lookup an entity by its symbol name. + * This looks up an entity by symbol stored in (EcsIdentifier, EcsSymbol). The + * operation does not take into account hierarchies. + * + * This operation can be useful to resolve, for example, a type by its C + * identifier, which does not include the Flecs namespacing. + * + * @param world The world. + * @param symbol The symbol. + * @param lookup_as_path If not found as a symbol, lookup as path. + * @param recursive If looking up as path, recursively traverse up the tree. + * @return The entity if found, else 0. + */ +FLECS_API +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *symbol, + bool lookup_as_path, + bool recursive); + +/** 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 sep The separator to use between path elements. + * @param prefix The initial character to use for root elements. + * @return The relative entity path. + */ +FLECS_API +char* ecs_get_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix); + +/** Write path identifier to buffer. + * Same as ecs_get_path_w_sep(), but writes result to an ecs_strbuf_t. + * + * @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 sep The separator to use between path elements. + * @param prefix The initial character to use for root elements. + * @param buf The buffer to write to. + */ +void ecs_get_path_w_sep_buf( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf); + +/** 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); + +/** 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); + +/** 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( + const 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); + +/** Set search path for lookup operations. + * This operation accepts an array of entity ids that will be used as search + * scopes by lookup operations. The operation returns the current search path. + * It is good practice to restore the old search path. + * + * The search path will be evaluated starting from the last element. + * + * The default search path includes flecs.core. When a custom search path is + * provided it overwrites the existing search path. Operations that rely on + * looking up names from flecs.core without providing the namespace may fail if + * the custom search path does not include flecs.core (EcsFlecsCore). + * + * The search path array is not copied into managed memory. The application must + * ensure that the provided array is valid for as long as it is used as the + * search path. + * + * The provided array must be terminated with a 0 element. This enables an + * application to push/pop elements to an existing array without invoking the + * ecs_set_lookup_path() operation again. + * + * @param world The world. + * @param lookup_path 0-terminated array with entity ids for the lookup path. + * @return Current lookup path array. + */ +FLECS_API +ecs_entity_t* ecs_set_lookup_path( + ecs_world_t *world, + const ecs_entity_t *lookup_path); + +/** Get current lookup path. + * Returns value set by ecs_set_lookup_path(). + * + * @param world The world. + * @return The current lookup path. + */ +FLECS_API +ecs_entity_t* ecs_get_lookup_path( + const ecs_world_t *world); + +/** @} */ + +/** @} */ + +/** + * @defgroup components Components + * Functions for registering and working with components. + * + * @{ + */ + +/** Find or create a component. + * This operation creates a new component, or finds an existing one. The find or + * create behavior is the same as ecs_entity_init(). + * + * When an existing component is found, the size and alignment are verified with + * the provided values. If the values do not match, the operation will fail. + * + * See the documentation of ecs_component_desc_t for more details. + * + * @param world The world. + * @param desc Component init parameters. + * @return A handle to the new or existing component, or 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_component_init( + ecs_world_t *world, + const ecs_component_desc_t *desc); + +/** Get the type for an id. + * This function returns the type information for an id. The specified id can be + * any valid id. For the rules on how type information is determined based on + * id, see ecs_get_typeid(). + * + * @param world The world. + * @param id The id. + * @return The type information of the id. + */ +FLECS_API +const ecs_type_info_t* ecs_get_type_info( + const ecs_world_t *world, + ecs_id_t id); + +/** Register hooks for component. + * Hooks allow for the execution of user code when components are constructed, + * copied, moved, destructed, added, removed or set. Hooks can be assigned as + * as long as a component has not yet been used (added to an entity). + * + * The hooks that are currently set can be accessed with ecs_get_type_info(). + * + * @param world The world. + * @param id The component id for which to register the actions + * @param hooks Type that contains the component actions. + */ +FLECS_API +void ecs_set_hooks_id( + ecs_world_t *world, + ecs_entity_t id, + const ecs_type_hooks_t *hooks); + +/** Get hooks for component. + * + * @param world The world. + * @param id The component id for which to retrieve the hooks. + * @return The hooks for the component, or NULL if not registered. + */ +FLECS_API +const ecs_type_hooks_t* ecs_get_hooks_id( + ecs_world_t *world, + ecs_entity_t id); + +/** @} */ + +/** + * @defgroup ids Ids + * Functions for working with `ecs_id_t`. + * + * @{ + */ + +/** Returns whether specified id a tag. + * This operation returns whether the specified type is a tag (a component + * without data/size). + * + * An id is a tag when: + * - it is an entity without the EcsComponent component + * - it has an EcsComponent with size member set to 0 + * - it is a pair where both elements are a tag + * - it is a pair where the first element has the EcsTag tag + * + * @param world The world. + * @param id The id. + * @return Whether the provided id is a tag. + */ +FLECS_API +bool ecs_id_is_tag( + const ecs_world_t *world, + ecs_id_t id); + +/** Return whether represents a union. + * This operation returns whether the specified type represents a union. Only + * pair ids can be unions. + * + * An id represents a union when: + * - The first element of the pair is EcsUnion/flecs::Union + * - The first element of the pair has EcsUnion/flecs::Union + * + * @param world The world. + * @param id The id. + * @return Whether the provided id represents a union. + */ +FLECS_API +bool ecs_id_is_union( + const ecs_world_t *world, + ecs_id_t id); + +/** Returns whether specified id is in use. + * This operation returns whether an id is in use in the world. An id is in use + * if it has been added to one or more tables. + * + * @param world The world. + * @param id The id. + * @return Whether the id is in use. + */ +FLECS_API +bool ecs_id_in_use( + const ecs_world_t *world, + ecs_id_t id); + +/** Get the type for an id. + * This operation returns the component id for an id, if the id is associated + * with a type. For a regular component with a non-zero size (an entity with the + * EcsComponent component) the operation will return the entity itself. + * + * For an entity that does not have the EcsComponent component, or with an + * EcsComponent value with size 0, the operation will return 0. + * + * For a pair id the operation will return the type associated with the pair, by + * applying the following rules in order: + * - The first pair element is returned if it is a component + * - 0 is returned if the relationship entity has the Tag property + * - The second pair element is returned if it is a component + * - 0 is returned. + * + * @param world The world. + * @param id The id. + * @return The type id of the id. + */ +FLECS_API +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id); + +/** Utility to match an id with a pattern. + * This operation returns true if the provided pattern matches the provided + * id. The pattern may contain a wildcard (or wildcards, when a pair). + * + * @param id The id. + * @param pattern The pattern to compare with. + */ +FLECS_API +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern); + +/** Utility to check if id is a pair. + * + * @param id The id. + * @return True if id is a pair. + */ +FLECS_API +bool ecs_id_is_pair( + ecs_id_t id); + +/** Utility to check if id is a wildcard. + * + * @param id The id. + * @return True if id is a wildcard or a pair containing a wildcard. + */ +FLECS_API +bool ecs_id_is_wildcard( + ecs_id_t id); + +/** Utility to check if id is valid. + * A valid id is an id that can be added to an entity. Invalid ids are: + * - ids that contain wildcards + * - ids that contain invalid entities + * - ids that are 0 or contain 0 entities + * + * Note that the same rules apply to removing from an entity, with the exception + * of wildcards. + * + * @param world The world. + * @param id The id. + * @return True if the id is valid. + */ +FLECS_API +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t id); + +/** Get flags associated with id. + * This operation returns the internal flags (see api_flags.h) that are + * associated with the provided id. + * + * @param world The world. + * @param id The id. + * @return Flags associated with the id, or 0 if the id is not in use. + */ +FLECS_API +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, + ecs_id_t id); + +/** Convert id flag to string. + * This operation converts a id flag to a string. + * + * @param id_flags The id flag. + * @return The id flag string, or NULL if no valid id is provided. + */ +FLECS_API +const char* ecs_id_flag_str( + ecs_id_t id_flags); + +/** Convert id to string. + * This operation interprets the structure of an id and converts it to a string. + * + * @param world The world. + * @param id The id to convert to a string. + * @return The id converted to a string. + */ +FLECS_API +char* ecs_id_str( + const ecs_world_t *world, + ecs_id_t id); + +/** Write id string to buffer. + * Same as ecs_id_str() but writes result to ecs_strbuf_t. + * + * @param world The world. + * @param id The id to convert to a string. + * @param buf The buffer to write to. + */ +FLECS_API +void ecs_id_str_buf( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf); + +/** @} */ + +/** + * @defgroup filters Filters + * Functions for working with `ecs_term_t` and `ecs_filter_t`. + * + * @{ + */ + +/** Iterator for a single (component) id. + * A term iterator returns all entities (tables) that match a single (component) + * id. The search for the matching set of entities (tables) is performed in + * constant time. + * + * @param world The world. + * @param term The term. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_term_iter( + const ecs_world_t *world, + ecs_term_t *term); + +/** Return a chained term iterator. + * A chained iterator applies a filter to the results of the input iterator. The + * resulting iterator must be iterated with ecs_term_next(). + * + * @param it The input iterator + * @param term The term filter to apply to the iterator. + * @return The chained iterator. + */ +FLECS_API +ecs_iter_t ecs_term_chain_iter( + const ecs_iter_t *it, + ecs_term_t *term); + +/** Progress a term iterator. + * This operation progresses the term iterator to the next table. The + * iterator must have been initialized with ecs_term_iter(). This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param it The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_term_next( + ecs_iter_t *it); + +/** Iterator for a parent's children. + * This operation is equivalent to a term iterator for (ChildOf, parent). + * Iterate the result with ecs_children_next(). + * + * @param world The world. + * @param parent The parent for which to iterate the children. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_children( + const ecs_world_t *world, + ecs_entity_t parent); + +/** Progress a children iterator. + * Equivalent to ecs_term_next(). + * + * @param it The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_children_next( + ecs_iter_t *it); + +/** Test whether term id is set. + * + * @param id The term id. + * @return True when set, false when not set. + */ +FLECS_API +bool ecs_term_id_is_set( + const ecs_term_id_t *id); + +/** Test whether a term is set. + * This operation can be used to test whether a term has been initialized with + * values or whether it is empty. + * + * An application generally does not need to invoke this operation. It is useful + * when initializing a 0-initialized array of terms (like in ecs_term_desc_t) as + * this operation can be used to find the last initialized element. + * + * @param term The term. + * @return True when set, false when not set. + */ +FLECS_API +bool ecs_term_is_initialized( + const ecs_term_t *term); + +/** Is term matched on $this variable. + * This operation checks whether a term is matched on the $this variable, which + * is the default source for queries. + * + * A term has a $this source when: + * - ecs_term_t::src::id is EcsThis + * - ecs_term_t::src::flags is EcsIsVariable + * + * If ecs_term_t::src is not populated, it will be automatically initialized to + * the $this source for the created query. + * + * @param term The term. + * @return True if term matches $this, false if not. + */ +FLECS_API +bool ecs_term_match_this( + const ecs_term_t *term); + +/** Is term matched on 0 source. + * This operation checks whether a term is matched on a 0 source. A 0 source is + * a term that isn't matched against anything, and can be used just to pass + * (component) ids to a query iterator. + * + * A term has a 0 source when: + * - ecs_term_t::src::id is 0 + * - ecs_term_t::src::flags has EcsIsEntity set + * + * @param term The term. + * @return True if term has 0 source, false if not. + */ +FLECS_API +bool ecs_term_match_0( + const ecs_term_t *term); + +/** Finalize term. + * Ensure that all fields of a term are consistent and filled out. This + * operation should be invoked before using and after assigning members to, or + * parsing a term. When a term contains unresolved identifiers, this operation + * will resolve and assign the identifiers. If the term contains any identifiers + * that cannot be resolved, the operation will fail. + * + * An application generally does not need to invoke this operation as the APIs + * that use terms (such as filters, queries and triggers) will finalize terms + * when they are created. + * + * The name and expr parameters are optional, and only used for giving more + * descriptive error messages. + * + * @param world The world. + * @param term The term to finalize. + * @return Zero if success, nonzero if an error occurred. + */ +FLECS_API +int ecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term); + +/** Copy resources of a term to another term. + * This operation copies one term to another term. If the source term contains + * allocated resources (such as identifiers), they will be duplicated so that + * no memory is shared between the terms. + * + * @param src The term to copy from. + * @return The destination term. + */ +FLECS_API +ecs_term_t ecs_term_copy( + const ecs_term_t *src); + +/** Move resources of a term to another term. + * Same as copy, but moves resources from src, if src->move is set to true. If + * src->move is not set to true, this operation will do a copy. + * + * The conditional move reduces redundant allocations in scenarios where a list + * of terms is partially created with allocated resources. + * + * @param src The term to move from. + * @return The destination term. + */ +FLECS_API +ecs_term_t ecs_term_move( + ecs_term_t *src); + +/** Free resources of term. + * This operation frees all resources (such as identifiers) of a term. The term + * itself is not freed. + * + * @param term The term to free. + */ +FLECS_API +void ecs_term_fini( + ecs_term_t *term); + +/** Initialize filter + * A filter is a lightweight object that can be used to query for entities in + * a world. Filters, as opposed to queries, do not cache results. They are + * therefore slower to iterate, but are faster to create. + * + * When a filter is copied by value, make sure to use ecs_filter_move() to + * ensure that the terms pointer still points to the inline array: + * + * @code + * ecs_filter_move(&dst_filter, &src_filter) + * @endcode + * + * Alternatively, the ecs_filter_move() function can be called with both arguments + * set to the same filter, to ensure the pointer is valid: + * + * @code + * ecs_filter_move(&f, &f) + * @endcode + * + * It is possible to create a filter without allocating any memory, by setting + * the .storage member in ecs_filter_desc_t. See the documentation for the + * member for more details. + * + * @param world The world. + * @param desc Properties for the filter to create. + * @return The filter if successful, NULL if not successful. + */ +FLECS_API +ecs_filter_t * ecs_filter_init( + ecs_world_t *world, + const ecs_filter_desc_t *desc); + +/** Deinitialize filter. + * Free resources associated with filter. + * + * @param filter The filter to deinitialize. + */ +FLECS_API +void ecs_filter_fini( + ecs_filter_t *filter); + +/** Finalize filter. + * When manually assigning an array of terms to the filter struct (so not when + * using ecs_filter_init()), this operation should be used to ensure that all + * terms are assigned properly and all (derived) fields have been set. + * + * When ecs_filter_init() is used to create the filter, this function should not + * be called. The purpose of this operation is to support creation of filters + * without allocating memory. + * + * @param filter The filter to finalize. + * @return Zero if filter is valid, non-zero if it contains errors. + * @ + */ +FLECS_API +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *filter); + +/** Find index for $this variable. + * This operation looks up the index of the $this variable. This index can + * be used in operations like ecs_iter_set_var() and ecs_iter_get_var(). + * + * The operation will return -1 if the variable was not found. This happens when + * a filter only has terms that are not matched on the $this variable, like a + * filter that exclusively matches singleton components. + * + * @param filter The rule. + * @return The index of the $this variable. + */ +FLECS_API +int32_t ecs_filter_find_this_var( + const ecs_filter_t *filter); + +/** Convert term to string expression. + * Convert term to a string expression. The resulting expression is equivalent + * to the same term, with the exception of And & Or operators. + * + * @param world The world. + * @param term The term. + * @return The term converted to a string. + */ +FLECS_API +char* ecs_term_str( + const ecs_world_t *world, + const ecs_term_t *term); + +/** Convert filter to string expression. + * Convert filter terms to a string expression. The resulting expression can be + * parsed to create the same filter. + * + * @param world The world. + * @param filter The filter. + * @return The filter converted to a string. + */ +FLECS_API +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** Return a filter iterator. + * A filter iterator lets an application iterate over entities that match the + * specified filter. + * + * @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( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** Return a chained filter iterator. + * A chained iterator applies a filter to the results of the input iterator. The + * resulting iterator must be iterated with ecs_filter_next(). + * + * @param it The input iterator + * @param filter The filter to apply to the iterator. + * @return The chained iterator. + */ +FLECS_API +ecs_iter_t ecs_filter_chain_iter( + const ecs_iter_t *it, + const ecs_filter_t *filter); + +/** Get pivot term for filter. + * The pivot term is the term that matches the smallest set of tables, and is + * a good default starting point for a search. + * + * The following conditions must be met for a term to be considered as pivot: + * - It must have a This subject + * - It must have the And operator + * + * When a filter does not have any terms that match those conditions, it will + * return -1. + * + * If one or more terms in the filter have no matching tables the filter won't + * yield any results. In this case the operation will return -2 which gives a + * search function the option to early out. + * + * @param world The world. + * @param filter The filter. + * @return Index of the pivot term (use with filter->terms) + */ +FLECS_API +int32_t ecs_filter_pivot_term( + const 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 *it); + +/** Same as ecs_filter_next, but always instanced. + * See instanced property of ecs_filter_desc_t. + * + * @param it The iterator + * @return True if more data is available, false if not. + */ +FLECS_API +bool ecs_filter_next_instanced( + ecs_iter_t *it); + +/** Move resources of one filter to another. + * + * @param dst The destination filter. + * @param src The source filter. + */ +FLECS_API +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src); + +/** Copy resources of one filter to another. + * + * @param dst The destination filter. + * @param src The source filter. + */ +FLECS_API +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src); + +/** @} */ + +/** + * @defgroup queries Queries + * Functions for working with `ecs_query_t`. + * + * @{ + */ + +/** Create a query. + * This operation creates a query. Queries are used to iterate over entities + * that match a filter 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 "pre-matched", 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 important 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_fini()) before the world is deleted, it will be deleted + * automatically. + * + * @param world The world. + * @param desc A structure describing the query properties. + * @return The new query. + */ +FLECS_API +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *desc); + +/** Destroy a query. + * This operation destroys a query and its resources. If the query is used as + * the parent of subqueries, those subqueries will be orphaned and must be + * deinitialized as well. + * + * @param query The query. + */ +FLECS_API +void ecs_query_fini( + ecs_query_t *query); + +/** Get filter from a query. + * This operation obtains a pointer to the internally constructed filter + * of the query and can be used to introspect the query terms. + * + * @param query The query. + * @return The filter. + */ +FLECS_API +const ecs_filter_t* ecs_query_get_filter( + const 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 world The world or stage, when iterating in readonly mode. + * @param query The query to iterate. + * @return The query iterator. + */ +FLECS_API +ecs_iter_t ecs_query_iter( + const ecs_world_t *world, + ecs_query_t *query); + +/** 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); + +/** Same as ecs_query_next, but always instanced. + * See "instanced" property of ecs_filter_desc_t. + * + * @param iter The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next_instanced( + ecs_iter_t *iter); + +/** Fast alternative to ecs_query_next() that only returns matched tables. + * This operation only populates the ecs_iter_t::table field. To access the + * matched components, call ecs_query_populate(). + * + * If this operation is used with a query that has inout/out terms, those terms + * will not be marked dirty unless ecs_query_populate() is called. + * + * @param iter The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next_table( + ecs_iter_t *iter); + +/** Populate iterator fields. + * This operation can be combined with ecs_query_next_table() to populate the + * iterator fields for the current table. + * + * Populating fields conditionally can save time when a query uses change + * detection, and only needs iterator data when the table has changed. When this + * operation is called, inout/out terms will be marked dirty. + * + * In cases where inout/out terms are conditionally written and no changes + * were made after calling ecs_query_populate(), the ecs_query_skip() function can + * be called to prevent the matched table components from being marked dirty. + * + * This operation does should not be used with queries that match disabled + * components, union relationships, or with queries that use order_by. + * + * When the when_changed argument is set to true, the iterator data will only + * populate when the data has changed, using query change detection. + * + * @param iter The iterator. + * @param when_changed Only populate data when result has changed. + */ +FLECS_API +int ecs_query_populate( + ecs_iter_t *iter, + bool when_changed); + +/** Returns whether the query data changed since the last iteration. + * The operation will return true after: + * - new entities have been matched with + * - new tables have been matched/unmatched with + * - matched entities were deleted + * - matched components were changed + * + * The operation will not return true after a write-only (EcsOut) or filter + * (EcsInOutNone) term has changed, when a term is not matched with the + * current table (This subject) or for tag terms. + * + * The changed state of a table is reset after it is iterated. If a iterator was + * not iterated until completion, tables may still be marked as changed. + * + * If no iterator is provided the operation will return the changed state of the + * all matched tables of the query. + * + * If an iterator is provided, the operation will return the changed state of + * the currently returned iterator result. The following preconditions must be + * met before using an iterator with change detection: + * + * - The iterator is a query iterator (created with ecs_query_iter()) + * - The iterator must be valid (ecs_query_next() must have returned true) + * - The iterator must be instanced + * + * @param query The query (optional if 'it' is provided). + * @param it The iterator result to test (optional if 'query' is provided). + * @return true if entities changed, otherwise false. + */ +FLECS_API +bool ecs_query_changed( + ecs_query_t *query, + const ecs_iter_t *it); + +/** Skip a table while iterating. + * This operation lets the query iterator know that a table was skipped while + * iterating. A skipped table will not reset its changed state, and the query + * will not update the dirty flags of the table for its out columns. + * + * Only valid iterators must be provided (next has to be called at least once & + * return true) and the iterator must be a query iterator. + * + * @param it The iterator result to skip. + */ +FLECS_API +void ecs_query_skip( + ecs_iter_t *it); + +/** Set group to iterate for query iterator. + * This operation limits the results returned by the query to only the selected + * group id. The query must have a group_by function, and the iterator must + * be a query iterator. + * + * Groups are sets of tables that are stored together in the query cache based + * on a group id, which is calculated per table by the group_by function. To + * iterate a group, an iterator only needs to know the first and last cache node + * for that group, which can both be found in a fast O(1) operation. + * + * As a result, group iteration is one of the most efficient mechanisms to + * filter out large numbers of entities, even if those entities are distributed + * across many tables. This makes it a good fit for things like dividing up + * a world into cells, and only iterating cells close to a player. + * + * The group to iterate must be set before the first call to ecs_query_next(). No + * operations that can add/remove components should be invoked between calling + * ecs_query_set_group() and ecs_query_next(). + * + * @param it The query iterator. + * @param group_id The group to iterate. + */ +FLECS_API +void ecs_query_set_group( + ecs_iter_t *it, + uint64_t group_id); + +/** Get context of query group. + * This operation returns the context of a query group as returned by the + * on_group_create callback. + * + * @param query The query. + * @param group_id The group for which to obtain the context. + * @return The group context, NULL if the group doesn't exist. + */ +FLECS_API +void* ecs_query_get_group_ctx( + const ecs_query_t *query, + uint64_t group_id); + +/** Get information about query group. + * This operation returns information about a query group, including the group + * context returned by the on_group_create callback. + * + * @param query The query. + * @param group_id The group for which to obtain the group info. + * @return The group info, NULL if the group doesn't exist. + */ +FLECS_API +const ecs_query_group_info_t* ecs_query_get_group_info( + const ecs_query_t *query, + uint64_t group_id); + +/** 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( + const ecs_query_t *query); + +/** Convert query to string. + * + * @param query The query. + * @return The query string. + */ +FLECS_API +char* ecs_query_str( + const ecs_query_t *query); + +/** Returns number of tables query matched with. + * + * @param query The query. + * @return The number of matched tables. + */ +FLECS_API +int32_t ecs_query_table_count( + const ecs_query_t *query); + +/** Returns number of empty tables query matched with. + * + * @param query The query. + * @return The number of matched empty tables. + */ +FLECS_API +int32_t ecs_query_empty_table_count( + const ecs_query_t *query); + +/** Returns number of entities query matched with. + * This operation iterates all non-empty tables in the query cache to find the + * total number of entities. + * + * @param query The query. + * @return The number of matched entities. + */ +FLECS_API +int32_t ecs_query_entity_count( + const ecs_query_t *query); + +/** Get query ctx. + * Return the value set in ecs_query_desc_t::ctx. + * + * @param query The query. + * @return The context. + */ +FLECS_API +void* ecs_query_get_ctx( + const ecs_query_t *query); + +/** Get query binding ctx. + * Return the value set in ecs_query_desc_t::binding_ctx. + * + * @param query The query. + * @return The context. + */ +FLECS_API +void* ecs_query_get_binding_ctx( + const ecs_query_t *query); + +/** @} */ + +/** + * @defgroup observers Observers + * Functions for working with events and observers. + * + * @{ + */ + +/** Send event. + * This sends an event to matching triggers & is the mechanism used by flecs + * itself to send OnAdd, OnRemove, etc events. + * + * Applications can use this function to send custom events, where a custom + * event can be any regular entity. + * + * Applications should not send builtin flecs events, as this may violate + * assumptions the code makes about the conditions under which those events are + * sent. + * + * Triggers are invoked synchronously. It is therefore safe to use stack-based + * data as event context, which can be set in the "param" member. + * + * @param world The world. + * @param desc Event parameters. + */ +FLECS_API +void ecs_emit( + ecs_world_t *world, + ecs_event_desc_t *desc); + +FLECS_API +void ecs_enqueue( + ecs_world_t *world, + ecs_event_desc_t *desc); + +/** Create observer. + * Observers are like triggers, but can subscribe for multiple terms. An + * observer only triggers when the source of the event meets all terms. + * + * See the documentation for ecs_observer_desc_t for more details. + * + * @param world The world. + * @param desc The observer creation parameters. + */ +FLECS_API +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc); + +/** Default run action for observer. + * This function can be called from a custom observer run action (see + * ecs_observer_desc_t::run for more details). This function ensures that the + * observer's filter is applied to the iterator's table, filters out duplicate + * events and implements EcsMonitor logic. + * + * @param it The iterator. + * @return True if the observer was invoked. + */ +FLECS_API +bool ecs_observer_default_run_action( + ecs_iter_t *it); + +/** Get observer ctx. + * Return the value set in ecs_observer_desc_t::ctx. + * + * @param world The world. + * @param observer The observer. + * @return The context. + */ +FLECS_API +void* ecs_observer_get_ctx( + const ecs_world_t *world, + ecs_entity_t observer); + +/** Get observer binding ctx. + * Return the value set in ecs_observer_desc_t::binding_ctx. + * + * @param world The world. + * @param observer The observer. + * @return The context. + */ +FLECS_API +void* ecs_observer_get_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer); + +/** @} */ + +/** + * @defgroup iterator Iterators + * Functions for working with `ecs_iter_t`. + * + * @{ + */ + +/** Create iterator from poly object. + * The provided poly object must have the iterable mixin. If an object is + * provided that does not have the mixin, the function will assert. + * + * When a filter is provided, an array of two iterators must be passed to the + * function. This allows the mixin implementation to create a chained iterator + * when necessary, which requires two iterator objects. + * + * If a filter is provided, the first element in the array of two iterators is + * the one that should be iterated. The mixin implementation may or may not set + * the second element, depending on whether an iterator chain is required. + * + * Additionally, when a filter is provided the returned iterator will be for a + * single term with the provided filter id. If the iterator is chained, the + * previous iterator in the chain can be accessed through it->chain_it. + * + * @param world The world or stage for which to create the iterator. + * @param poly The poly object from which to create the iterator. + * @param iter The iterator (out, ecs_iter_t[2] when filter is set). + * @param filter Optional term used for filtering the results. + */ +FLECS_API +void ecs_iter_poly( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter, + ecs_term_t *filter); + +/** Progress any iterator. + * This operation is useful in combination with iterators for which it is not + * known what created them. Example use cases are functions that should accept + * any kind of iterator (such as serializers) or iterators created from poly + * objects. + * + * This operation is slightly slower than using a type-specific iterator (e.g. + * ecs_filter_next(), ecs_query_next()) as it has to call a function pointer which + * introduces a level of indirection. + * + * @param it The iterator. + * @return True if iterator has more results, false if not. + */ +FLECS_API +bool ecs_iter_next( + ecs_iter_t *it); + +/** Cleanup iterator resources. + * This operation cleans up any resources associated with the iterator. + * + * This operation should only be used when an iterator is not iterated until + * completion (next has not yet returned false). When an iterator is iterated + * until completion, resources are automatically freed. + * + * @param it The iterator. + */ +FLECS_API +void ecs_iter_fini( + ecs_iter_t *it); + +/** Count number of matched entities in query. + * This operation returns the number of matched entities. If a query contains no + * matched entities but still yields results (e.g. it has no terms with This + * sources) the operation will return 0. + * + * To determine the number of matched entities, the operation iterates the + * iterator until it yields no more results. + * + * @param it The iterator. + * @return True if iterator has more results, false if not. + */ +FLECS_API +int32_t ecs_iter_count( + ecs_iter_t *it); + +/** Test if iterator is true. + * This operation will return true if the iterator returns at least one result. + * This is especially useful in combination with fact-checking rules (see the + * rules addon). + * + * The operation requires a valid iterator. After the operation is invoked, the + * application should no longer invoke next on the iterator and should treat it + * as if the iterator is iterated until completion. + * + * @param it The iterator. + * @return true if the iterator returns at least one result. + */ +FLECS_API +bool ecs_iter_is_true( + ecs_iter_t *it); + +/** Get first matching entity from iterator. + * After this operation the application should treat the iterator as if it has + * been iterated until completion. + * + * @param it The iterator. + * @return The first matching entity, or 0 if no entities were matched. + */ +FLECS_API +ecs_entity_t ecs_iter_first( + ecs_iter_t *it); + +/** Set value for iterator variable. + * This constrains the iterator to return only results for which the variable + * equals the specified value. The default value for all variables is + * EcsWildcard, which means the variable can assume any value. + * + * Example: + * + * @code + * // Rule that matches (Eats, *) + * ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ + * .terms = { + * { .first.id = Eats, .second.name = "$food" } + * } + * }); + * + * int food_var = ecs_rule_find_var(r, "food"); + * + * // Set Food to Apples, so we're only matching (Eats, Apples) + * ecs_iter_t it = ecs_rule_iter(world, r); + * ecs_iter_set_var(&it, food_var, Apples); + * + * while (ecs_rule_next(&it)) { + * for (int i = 0; i < it.count; i ++) { + * // iterate as usual + * } + * } + * @endcode + * + * The variable must be initialized after creating the iterator and before the + * first call to next. + * + * @param it The iterator. + * @param var_id The variable index. + * @param entity The entity variable value. + */ +FLECS_API +void ecs_iter_set_var( + ecs_iter_t *it, + int32_t var_id, + ecs_entity_t entity); + +/** Same as ecs_iter_set_var(), but for a table. + * This constrains the variable to all entities in a table. + * + * @param it The iterator. + * @param var_id The variable index. + * @param table The table variable value. + */ +FLECS_API +void ecs_iter_set_var_as_table( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_t *table); + +/** Same as ecs_iter_set_var(), but for a range of entities + * This constrains the variable to a range of entities in a table. + * + * @param it The iterator. + * @param var_id The variable index. + * @param range The range variable value. + */ +FLECS_API +void ecs_iter_set_var_as_range( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_range_t *range); + +/** Get value of iterator variable as entity. + * A variable can be interpreted as entity if it is set to an entity, or if it + * is set to a table range with count 1. + * + * This operation can only be invoked on valid iterators. The variable index + * must be smaller than the total number of variables provided by the iterator + * (as set in ecs_iter_t::variable_count). + * + * @param it The iterator. + * @param var_id The variable index. + * @return The variable value. + */ +FLECS_API +ecs_entity_t ecs_iter_get_var( + ecs_iter_t *it, + int32_t var_id); + +/** Get value of iterator variable as table. + * A variable can be interpreted as table if it is set as table range with + * both offset and count set to 0, or if offset is 0 and count matches the + * number of elements in the table. + * + * This operation can only be invoked on valid iterators. The variable index + * must be smaller than the total number of variables provided by the iterator + * (as set in ecs_iter_t::variable_count). + * + * @param it The iterator. + * @param var_id The variable index. + * @return The variable value. + */ +FLECS_API +ecs_table_t* ecs_iter_get_var_as_table( + ecs_iter_t *it, + int32_t var_id); + +/** Get value of iterator variable as table range. + * A value can be interpreted as table range if it is set as table range, or if + * it is set to an entity with a non-empty type (the entity must have at least + * one component, tag or relationship in its type). + * + * This operation can only be invoked on valid iterators. The variable index + * must be smaller than the total number of variables provided by the iterator + * (as set in ecs_iter_t::variable_count). + * + * @param it The iterator. + * @param var_id The variable index. + * @return The variable value. + */ +FLECS_API +ecs_table_range_t ecs_iter_get_var_as_range( + ecs_iter_t *it, + int32_t var_id); + +/** Returns whether variable is constrained. + * This operation returns true for variables set by one of the ecs_iter_set_var* + * operations. + * + * A constrained variable is guaranteed not to change values while results are + * being iterated. + * + * @param it The iterator. + * @param var_id The variable index. + * @return Whether the variable is constrained to a specified value. + */ +FLECS_API +bool ecs_iter_var_is_constrained( + ecs_iter_t *it, + int32_t var_id); + +/** Convert iterator to string. + * Prints the contents of an iterator to a string. Useful for debugging and/or + * testing the output of an iterator. + * + * The function only converts the currently iterated data to a string. To + * convert all data, the application has to manually call the next function and + * call ecs_iter_str() on each result. + * + * @param it The iterator. + * @return A string representing the contents of the iterator. + */ +FLECS_API +char* ecs_iter_str( + const ecs_iter_t *it); + +/** Create a paged iterator. + * Paged iterators limit the results to those starting from 'offset', and will + * return at most 'limit' results. + * + * The iterator must be iterated with ecs_page_next(). + * + * A paged iterator acts as a passthrough for data exposed by the parent + * iterator, so that any data provided by the parent will also be provided by + * the paged iterator. + * + * @param it The source iterator. + * @param offset The number of entities to skip. + * @param limit The maximum number of entities to iterate. + * @return A page iterator. + */ +FLECS_API +ecs_iter_t ecs_page_iter( + const ecs_iter_t *it, + int32_t offset, + int32_t limit); + +/** Progress a paged iterator. + * Progresses an iterator created by ecs_page_iter(). + * + * @param it The iterator. + * @return true if iterator has more results, false if not. + */ +FLECS_API +bool ecs_page_next( + ecs_iter_t *it); + +/** Create a worker iterator. + * Worker iterators can be used to equally divide the number of matched entities + * across N resources (usually threads). Each resource will process the total + * number of matched entities divided by 'count'. + * + * Entities are distributed across resources such that the distribution is + * stable between queries. Two queries that match the same table are guaranteed + * to match the same entities in that table. + * + * The iterator must be iterated with ecs_worker_next(). + * + * A worker iterator acts as a passthrough for data exposed by the parent + * iterator, so that any data provided by the parent will also be provided by + * the worker iterator. + * + * @param it The source iterator. + * @param index The index of the current resource. + * @param count The total number of resources to divide entities between. + * @return A worker iterator. + */ +FLECS_API +ecs_iter_t ecs_worker_iter( + const ecs_iter_t *it, + int32_t index, + int32_t count); + +/** Progress a worker iterator. + * Progresses an iterator created by ecs_worker_iter(). + * + * @param it The iterator. + * @return true if iterator has more results, false if not. + */ +FLECS_API +bool ecs_worker_next( + ecs_iter_t *it); + +/** Obtain data for a query field. + * This operation retrieves a pointer to an array of data that belongs to the + * term in the query. The index refers to the location of the term in the query, + * and starts counting from one. + * + * For example, the query "Position, Velocity" will return the Position array + * for index 1, and the Velocity array for index 2. + * + * When the specified field is not owned by the entity this function returns a + * pointer instead of an array. This happens when the source of a field is not + * the entity being iterated, such as a shared component (from a prefab), a + * component from a parent, or another entity. The ecs_field_is_self() operation + * can be used to test dynamically if a field is owned. + * + * The provided size must be either 0 or must match the size of the datatype + * of the returned array. If the size does not match, the operation may assert. + * The size can be dynamically obtained with ecs_field_size. + * + * @param it The iterator. + * @param size The type size of the requested data. + * @param index The index of the field in the iterator. + * @return A pointer to the data of the field. + */ +FLECS_API +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index); + +/** Test whether the field is readonly. + * This operation returns whether the field is readonly. Readonly fields are + * annotated with [in], or are added as a const type in the C++ API. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is readonly. + */ +FLECS_API +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int32_t index); + +/** Test whether the field is writeonly. + * This operation returns whether this is a writeonly field. Writeonly terms are + * annotated with [out]. + * + * Serializers are not required to serialize the values of a writeonly field. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is writeonly. + */ +FLECS_API +bool ecs_field_is_writeonly( + const ecs_iter_t *it, + int32_t index); + +/** Test whether field is set. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is set. + */ +FLECS_API +bool ecs_field_is_set( + const ecs_iter_t *it, + int32_t index); + +/** Return id matched for field. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The id matched for the field. + */ +FLECS_API +ecs_id_t ecs_field_id( + const ecs_iter_t *it, + int32_t index); + +/** Return index of matched table column. + * This function only returns column indices for fields that have been matched + * on the $this variable. Fields matched on other tables will return -1. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The index of the matched column, -1 if not matched. + */ +FLECS_API +int32_t ecs_field_column_index( + const ecs_iter_t *it, + int32_t index); + +/** Return field source. + * The field source is the entity on which the field was matched. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The source for the field. + */ +FLECS_API +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int32_t index); + +/** Return field type size. + * Return type size of the field. Returns 0 if the field has no data. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The type size for the field. + */ +FLECS_API +size_t ecs_field_size( + const ecs_iter_t *it, + int32_t index); + +/** Test whether the field is matched on self. + * This operation returns whether the field is matched on the currently iterated + * entity. This function will return false when the field is owned by another + * entity, such as a parent or a prefab. + * + * When this operation returns false, the field must be accessed as a single + * value instead of an array. Fields for which this operation returns true + * return arrays with it->count values. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is matched on self. + */ +FLECS_API +bool ecs_field_is_self( + const ecs_iter_t *it, + int32_t index); + +/** @} */ + +/** + * @defgroup tables Tables + * Functions for working with `ecs_table_t`. + * + * @{ + */ + +/** Get type for table. + * The table type is a vector that contains all component, tag and pair ids. + * + * @param table The table. + * @return The type of the table. + */ +FLECS_API +const ecs_type_t* ecs_table_get_type( + const ecs_table_t *table); + +/** Get type index for id. + * This operation returns the index for an id in the table's type. + * + * @param world The world. + * @param table The table. + * @param id The id. + * @return The index of the id in the table type, or -1 if not found. + */ +FLECS_API +int32_t ecs_table_get_type_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +/** Get column index for id. + * This operation returns the column index for an id in the table's type. If the + * id is not a component, the function will return -1. + * + * @param world The world. + * @param table The table. + * @param id The component id. + * @return The column index of the id, or -1 if not found/not a component. + */ +FLECS_API +int32_t ecs_table_get_column_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +/** Return number of columns in table. + * Similar to ecs_table_get_type(table)->count, except that the column count + * only counts the number of components in a table. + * + * @param table The table. + * @return The number of columns in the table. + */ +FLECS_API +int32_t ecs_table_column_count( + const ecs_table_t *table); + +/** Convert type index to column index. + * Tables have an array of columns for each component in the table. This array + * does not include elements for tags, which means that the index for a + * component in the table type is not necessarily the same as the index in the + * column array. This operation converts from an index in the table type to an + * index in the column array. + * + * @param table The table. + * @param index The index in the table type. + * @return The index in the table column array. + */ +FLECS_API +int32_t ecs_table_type_to_column_index( + const ecs_table_t *table, + int32_t index); + +/** Convert column index to type index. + * Same as ecs_table_type_to_column_index(), but converts from an index in the + * column array to an index in the table type. + * + * @param table The table. + * @param index The column index. + * @return The index in the table type. + */ +FLECS_API +int32_t ecs_table_column_to_type_index( + const ecs_table_t *table, + int32_t index); + +/** Get column from table by column index. + * This operation returns the component array for the provided index. + * + * @param table The table. + * @param index The column index. + * @param offset The index of the first row to return (0 for entire column). + * @return The component array, or NULL if the index is not a component. + */ +FLECS_API +void* ecs_table_get_column( + const ecs_table_t *table, + int32_t index, + int32_t offset); + +/** Get column from table by component id. + * This operation returns the component array for the provided component id. + * + * @param table The table. + * @param id The component id for the column. + * @param offset The index of the first row to return (0 for entire column). + * @return The component array, or NULL if the index is not a component. + */ +FLECS_API +void* ecs_table_get_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t offset); + +/** Get column size from table. + * This operation returns the component size for the provided index. + * + * @param table The table. + * @param index The column index. + * @return The component size, or 0 if the index is not a component. + */ +FLECS_API +size_t ecs_table_get_column_size( + const ecs_table_t *table, + int32_t index); + +/** 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 table The table. + * @return The number of records in a table. + */ +FLECS_API +int32_t ecs_table_count( + const ecs_table_t *table); + +/** Test if table has id. + * Same as ecs_table_get_type_index(world, table, id) != -1. + * + * @param world The world. + * @param table The table. + * @param id The id. + * @return True if the table has the id, false if the table doesn't. + */ +FLECS_API +bool ecs_table_has_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +/** Return depth for table in tree for relationship rel. + * Depth is determined by counting the number of targets encountered while + * traversing up the relationship tree for rel. Only acyclic relationships are + * supported. + * + * @param world The world. + * @param table The table. + * @param rel The relationship. + * @return The depth of the table in the tree. + */ +FLECS_API +int32_t ecs_table_get_depth( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t rel); + +/** Get table that has all components of current table plus the specified id. + * If the provided table already has the provided id, the operation will return + * the provided table. + * + * @param world The world. + * @param table The table. + * @param id The id to add. + * @result The resulting table. + */ +FLECS_API +ecs_table_t* ecs_table_add_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id); + +/** Find table from id array. + * This operation finds or creates a table with the specified array of + * (component) ids. The ids in the array must be sorted, and it may not contain + * duplicate elements. + * + * @param world The world. + * @param ids The id array. + * @param id_count The number of elements in the id array. + * @return The table with the specified (component) ids. + */ +FLECS_API +ecs_table_t* ecs_table_find( + ecs_world_t *world, + const ecs_id_t *ids, + int32_t id_count); + +/** Get table that has all components of current table minus the specified id. + * If the provided table doesn't have the provided id, the operation will return + * the provided table. + * + * @param world The world. + * @param table The table. + * @param id The id to remove. + * @result The resulting table. + */ +FLECS_API +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id); + +/** Lock a table. + * When a table is locked, modifications to it will throw an assert. When the + * table is locked recursively, it will take an equal amount of unlock + * operations to actually unlock the table. + * + * Table locks can be used to build safe iterators where it is guaranteed that + * the contents of a table are not modified while it is being iterated. + * + * The operation only works when called on the world, and has no side effects + * when called on a stage. The assumption is that when called on a stage, + * operations are deferred already. + * + * @param world The world. + * @param table The table to lock. + */ +FLECS_API +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table); + +/** Unlock a table. + * Must be called after calling ecs_table_lock(). + * + * @param world The world. + * @param table The table to unlock. + */ +FLECS_API +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table); + +/** Test table for flags. + * Test if table has all of the provided flags. See + * include/flecs/private/api_flags.h for a list of table flags that can be used + * with this function. + * + * @param table The table. + * @param flags The flags to test for. + * @return Whether the specified flags are set for the table. + */ +FLECS_API +bool ecs_table_has_flags( + ecs_table_t *table, + ecs_flags32_t flags); + +/** Swaps two elements inside the table. This is useful for implementing custom + * table sorting algorithms. + * @param world The world + * @param table The table to swap elements in + * @param row_1 Table element to swap with row_2 + * @param row_2 Table element to swap with row_1 +*/ +FLECS_API +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2); + +/** Commit (move) entity to a table. + * This operation moves an entity from its current table to the specified + * table. This may cause the following actions: + * - Ctor for each component in the target table + * - Move for each overlapping component + * - Dtor for each component in the source table. + * - OnAdd triggers for non-overlapping components in the target table + * - OnRemove triggers for non-overlapping components in the source table. + * + * This operation is a faster than adding/removing components individually. + * + * The application must explicitly provide the difference in components between + * tables as the added/removed parameters. This can usually be derived directly + * from the result of ecs_table_add_id() and ecs_table_remove_id(). These arrays are + * required to properly execute OnAdd/OnRemove triggers. + * + * @param world The world. + * @param entity The entity to commit. + * @param record The entity's record (optional, providing it saves a lookup). + * @param table The table to commit the entity to. + * @return True if the entity got moved, false otherwise. + */ +FLECS_API +bool ecs_commit( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + const ecs_type_t *added, + const ecs_type_t *removed); + +/** Find record for entity. */ +FLECS_API +ecs_record_t* ecs_record_find( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get component pointer from column/record. */ +FLECS_API +void* ecs_record_get_column( + const ecs_record_t *r, + int32_t column, + size_t c_size); + +/** Search for component id in table type. + * This operation returns the index of first occurrence of the id in the table + * type. The id may be a wildcard. + * + * When id_out is provided, the function will assign it with the found id. The + * found id may be different from the provided id if it is a wildcard. + * + * This is a constant time operation. + * + * @param world The world. + * @param table The table. + * @param id The id to search for. + * @param id_out If provided, it will be set to the found id (optional). + * @return The index of the id in the table type. + */ +FLECS_API +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out); + +/** Search for component id in table type starting from an offset. + * This operation is the same as ecs_search(), but starts searching from an offset + * in the table type. + * + * This operation is typically called in a loop where the resulting index is + * used in the next iteration as offset: + * + * @code + * int32_t index = -1; + * while ((index = ecs_search_offset(world, table, offset, id, NULL))) { + * // do stuff + * } + * @endcode + * + * Depending on how the operation is used it is either linear or constant time. + * When the id has the form (id) or (rel, *) and the operation is invoked as + * in the above example, it is guaranteed to be constant time. + * + * If the provided id has the form (*, tgt) the operation takes linear time. The + * reason for this is that ids for an target are not packed together, as they + * are sorted relationship first. + * + * If the id at the offset does not match the provided id, the operation will do + * a linear search to find a matching id. + * + * @param world The world. + * @param table The table. + * @param offset Offset from where to start searching. + * @param id The id to search for. + * @param id_out If provided, it will be set to the found id (optional). + * @return The index of the id in the table type. + */ +FLECS_API +int32_t ecs_search_offset( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_t *id_out); + +/** Search for component/relationship id in table type starting from an offset. + * This operation is the same as ecs_search_offset(), but has the additional + * capability of traversing relationships to find a component. For example, if + * an application wants to find a component for either the provided table or a + * prefab (using the IsA relationship) of that table, it could use the operation + * like this: + * + * @code + * int32_t index = ecs_search_relation( + * world, // the world + * table, // the table + * 0, // offset 0 + * ecs_id(Position), // the component id + * EcsIsA, // the relationship to traverse + * 0, // start at depth 0 (the table itself) + * 0, // no depth limit + * NULL, // (optional) entity on which component was found + * NULL, // see above + * NULL); // internal type with information about matched id + * @endcode + * + * The operation searches depth first. If a table type has 2 IsA relationships, the + * operation will first search the IsA tree of the first relationship. + * + * When choosing between ecs_search(), ecs_search_offset() and ecs_search_relation(), + * the simpler the function the better its performance. + * + * @param world The world. + * @param table The table. + * @param offset Offset from where to start searching. + * @param id The id to search for. + * @param rel The relationship to traverse (optional). + * @param flags Whether to search EcsSelf and/or EcsUp. + * @param subject_out If provided, it will be set to the matched entity. + * @param id_out If provided, it will be set to the found id (optional). + * @param tr_out Internal datatype. + * @return The index of the id in the table type. + */ +FLECS_API +int32_t ecs_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, /* EcsSelf and/or EcsUp */ + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out); + +/** @} */ + +/** + * @defgroup values Values + * Construct, destruct, copy and move dynamically created values. + * + * @{ + */ + +/** Construct a value in existing storage + * + * @param world The world. + * @param type The type of the value to create. + * @param ptr Pointer to a value of type 'type' + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_init( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr); + +/** Construct a value in existing storage + * + * @param world The world. + * @param ti The type info of the type to create. + * @param ptr Pointer to a value of type 'type' + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_init_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr); + +/** Construct a value in new storage + * + * @param world The world. + * @param type The type of the value to create. + * @return Pointer to type if success, NULL if failed. + */ +FLECS_API +void* ecs_value_new( + ecs_world_t *world, + ecs_entity_t type); + +/** Construct a value in new storage + * + * @param world The world. + * @param ti The type info of the type to create. + * @return Pointer to type if success, NULL if failed. + */ +void* ecs_value_new_w_type_info( + ecs_world_t *world, + const ecs_type_info_t *ti); + +/** Destruct a value + * + * @param world The world. + * @param ti Type info of the value to destruct. + * @param ptr Pointer to constructed value of type 'type'. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_fini_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr); + +/** Destruct a value + * + * @param world The world. + * @param type The type of the value to destruct. + * @param ptr Pointer to constructed value of type 'type'. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_fini( + const ecs_world_t *world, + ecs_entity_t type, + void* ptr); + +/** Destruct a value, free storage + * + * @param world The world. + * @param type The type of the value to destruct. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_free( + ecs_world_t *world, + ecs_entity_t type, + void* ptr); + +/** Copy value. + * + * @param world The world. + * @param ti Type info of the value to copy. + * @param dst Pointer to the storage to copy to. + * @param src Pointer to the value to copy. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_copy_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + const void *src); + +/** Copy value. + * + * @param world The world. + * @param type The type of the value to copy. + * @param dst Pointer to the storage to copy to. + * @param src Pointer to the value to copy. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_copy( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + const void *src); + +/** Move value. + * + * @param world The world. + * @param ti Type info of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src); + +/** Move value. + * + * @param world The world. + * @param type The type of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src); + +/** Move construct value. + * + * @param world The world. + * @param ti Type info of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move_ctor_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src); + +/** Move construct value. + * + * @param world The world. + * @param type The type of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move_ctor( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src); + +/** @} */ + +/** @} */ + +/** + * @defgroup c_addons Addons + * @ingroup c + * C APIs for addons. + * + * @{ + * @} + */ + +/** + * @file addons/flecs_c.h + * @brief Extends the core API with convenience macros for C applications. + */ + +#ifndef FLECS_C_ +#define FLECS_C_ + +/** + * @defgroup flecs_c Macro API + * @ingroup c + * Convenience macro's for C API + * + * @{ + */ + +/** + * @defgroup flecs_c_creation Creation macro's + * Convenience macro's for creating entities, components and observers + * + * @{ + */ + +/* Use for declaring entity, tag, prefab / any other entity identifier */ +#define ECS_DECLARE(id)\ + ecs_entity_t id, ecs_id(id) + +/** Forward declare an entity. */ +#define ECS_ENTITY_DECLARE ECS_DECLARE + +/** Define a forward declared entity. + * + * Example: + * + * @code + * ECS_ENTITY_DEFINE(world, MyEntity, Position, Velocity); + * @endcode + */ +#define ECS_ENTITY_DEFINE(world, id_, ...) \ + { \ + ecs_entity_desc_t desc = {0}; \ + desc.id = id_; \ + desc.name = #id_; \ + desc.add_expr = #__VA_ARGS__; \ + id_ = ecs_entity_init(world, &desc); \ + ecs_id(id_) = id_; \ + ecs_assert(id_ != 0, ECS_INVALID_PARAMETER, NULL); \ + } \ + (void)id_; \ + (void)ecs_id(id_) + +/** Declare & define an entity. + * + * Example: + * + * @code + * ECS_ENTITY(world, MyEntity, Position, Velocity); + * @endcode + */ +#define ECS_ENTITY(world, id, ...) \ + ecs_entity_t ecs_id(id); \ + ecs_entity_t id = 0; \ + ECS_ENTITY_DEFINE(world, id, __VA_ARGS__) + +/** Forward declare a tag. */ +#define ECS_TAG_DECLARE ECS_DECLARE + +/** Define a forward declared tag. + * + * Example: + * + * @code + * ECS_TAG_DEFINE(world, MyTag); + * @endcode + */ +#define ECS_TAG_DEFINE(world, id) ECS_ENTITY_DEFINE(world, id, 0) + +/** Declare & define a tag. + * + * Example: + * + * @code + * ECS_TAG(world, MyTag); + * @endcode + */ +#define ECS_TAG(world, id) ECS_ENTITY(world, id, 0) + +/** Forward declare a prefab. */ +#define ECS_PREFAB_DECLARE ECS_DECLARE + +/** Define a forward declared prefab. + * + * Example: + * + * @code + * ECS_PREFAB_DEFINE(world, MyPrefab, Position, Velocity); + * @endcode + */ +#define ECS_PREFAB_DEFINE(world, id, ...) ECS_ENTITY_DEFINE(world, id, Prefab, __VA_ARGS__) + +/** Declare & define a prefab. + * + * Example: + * + * @code + * ECS_PREFAB(world, MyPrefab, Position, Velocity); + * @endcode + */ +#define ECS_PREFAB(world, id, ...) ECS_ENTITY(world, id, Prefab, __VA_ARGS__) + +/** Forward declare a component. */ +#define ECS_COMPONENT_DECLARE(id) ecs_entity_t ecs_id(id) + +/** Define a forward declared component. + * + * Example: + * + * @code + * ECS_COMPONENT_DEFINE(world, Position); + * @endcode + */ +#define ECS_COMPONENT_DEFINE(world, id_) \ + {\ + ecs_component_desc_t desc = {0}; \ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = ecs_id(id_); \ + edesc.use_low_id = true; \ + edesc.name = #id_; \ + edesc.symbol = #id_; \ + desc.entity = ecs_entity_init(world, &edesc); \ + desc.type.size = ECS_SIZEOF(id_); \ + desc.type.alignment = ECS_ALIGNOF(id_); \ + ecs_id(id_) = ecs_component_init(world, &desc);\ + }\ + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL) + +/** Declare & define a component. + * + * Example: + * + * @code + * ECS_COMPONENT(world, Position); + * @endcode + */ +#define ECS_COMPONENT(world, id)\ + ecs_entity_t ecs_id(id) = 0;\ + ECS_COMPONENT_DEFINE(world, id);\ + (void)ecs_id(id) + +/* Forward declare an observer. */ +#define ECS_OBSERVER_DECLARE(id) ecs_entity_t ecs_id(id) + +/** Define a forward declared observer. + * + * Example: + * + * @code + * ECS_OBSERVER_DEFINE(world, AddPosition, EcsOnAdd, Position); + * @endcode + */ +#define ECS_OBSERVER_DEFINE(world, id_, kind, ...)\ + {\ + ecs_observer_desc_t desc = {0};\ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = ecs_id(id_); \ + edesc.name = #id_; \ + desc.entity = ecs_entity_init(world, &edesc); \ + desc.callback = id_;\ + desc.filter.expr = #__VA_ARGS__;\ + desc.events[0] = kind;\ + ecs_id(id_) = ecs_observer_init(world, &desc);\ + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL);\ + } + +/** Declare & define an observer. + * + * Example: + * + * @code + * ECS_OBSERVER(world, AddPosition, EcsOnAdd, Position); + * @endcode + */ +#define ECS_OBSERVER(world, id, kind, ...)\ + ecs_entity_t ecs_id(id) = 0; \ + ECS_OBSERVER_DEFINE(world, id, kind, __VA_ARGS__);\ + ecs_entity_t id = ecs_id(id);\ + (void)ecs_id(id);\ + (void)id + +/** Shorthand for creating an entity with ecs_entity_init(). + * + * Example: + * + * @code + * ecs_entity(world, { + * .name = "MyEntity" + * }); + * @endcode + */ +#define ecs_entity(world, ...)\ + ecs_entity_init(world, &(ecs_entity_desc_t) __VA_ARGS__ ) + +/** Shorthand for creating a component with ecs_component_init(). + * + * Example: + * + * @code + * ecs_component(world, { + * .type.size = 4, + * .type.alignment = 4 + * }); + * @endcode + */ +#define ecs_component(world, ...)\ + ecs_component_init(world, &(ecs_component_desc_t) __VA_ARGS__ ) + +/** Shorthand for creating a component from a type. + * + * Example: + * + * @code + * ecs_component_t(world, Position); + * @endcode + */ +#define ecs_component_t(world, T)\ + ecs_component_init(world, &(ecs_component_desc_t) { \ + .entity = ecs_entity(world, { \ + .name = #T, \ + .symbol = #T, \ + .use_low_id = true \ + }), \ + .type.size = ECS_SIZEOF(T), \ + .type.alignment = ECS_ALIGNOF(T) \ + }) + +/** Shorthand for creating a filter with ecs_filter_init(). + * + * Example: + * + * @code + * ecs_filter(world, { + * .terms = {{ ecs_id(Position) }} + * }); + * @endcode + */ +#define ecs_filter(world, ...)\ + ecs_filter_init(world, &(ecs_filter_desc_t) __VA_ARGS__ ) + +/** Shorthand for creating a query with ecs_query_init(). + * + * Example: + * + * @code + * ecs_query(world, { + * .filter.terms = {{ ecs_id(Position) }} + * }); + * @endcode + */ +#define ecs_query(world, ...)\ + ecs_query_init(world, &(ecs_query_desc_t) __VA_ARGS__ ) + +/** Shorthand for creating an observer with ecs_observer_init(). + * + * Example: + * + * @code + * ecs_observer(world, { + * .filter.terms = {{ ecs_id(Position) }}, + * .events = { EcsOnAdd }, + * .callback = AddPosition + * }); + * @endcode + */ +#define ecs_observer(world, ...)\ + ecs_observer_init(world, &(ecs_observer_desc_t) __VA_ARGS__ ) + +/** @} */ + +/** + * @defgroup flecs_c_type_safe Type Safe API + * Macro's that wrap around core functions to provide a "type safe" API in C + * + * @{ + */ + +/** + * @defgroup flecs_c_entities Entity API + * @{ + */ + +/** + * @defgroup flecs_c_creation_deletion Creation & Deletion + * @{ + */ + +#define ecs_new(world, T) ecs_new_w_id(world, ecs_id(T)) + +#define ecs_new_w_pair(world, first, second)\ + ecs_new_w_id(world, ecs_pair(first, second)) + +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_id(world, ecs_id(component), count) + +#define ecs_new_entity(world, n)\ + ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = n,\ + }) + +#define ecs_new_prefab(world, n)\ + ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = n,\ + .add = {EcsPrefab}\ + }) + +#define ecs_delete_children(world, parent)\ + ecs_delete_with(world, ecs_pair(EcsChildOf, parent)) + +/** @} */ + +/** + * @defgroup flecs_c_adding_removing Adding & Removing + * @{ + */ + +#define ecs_add(world, entity, T)\ + ecs_add_id(world, entity, ecs_id(T)) + +#define ecs_add_pair(world, subject, first, second)\ + ecs_add_id(world, subject, ecs_pair(first, second)) + + +#define ecs_remove(world, entity, T)\ + ecs_remove_id(world, entity, ecs_id(T)) + +#define ecs_remove_pair(world, subject, first, second)\ + ecs_remove_id(world, subject, ecs_pair(first, second)) + + +#define ecs_override(world, entity, T)\ + ecs_override_id(world, entity, ecs_id(T)) + +#define ecs_override_pair(world, subject, first, second)\ + ecs_override_id(world, subject, ecs_pair(first, second)) + +/** @} */ + +/** + * @defgroup flecs_c_getting_setting Getting & Setting + * @{ + */ + +#define ecs_set_ptr(world, entity, component, ptr)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), ptr) + +#define ecs_set(world, entity, component, ...)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), &(component)__VA_ARGS__) + +#define ecs_set_pair(world, subject, First, second, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(ecs_id(First), second),\ + sizeof(First), &(First)__VA_ARGS__) + +#define ecs_set_pair_second(world, subject, first, Second, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(first, ecs_id(Second)),\ + sizeof(Second), &(Second)__VA_ARGS__) + +#define ecs_set_pair_object ecs_set_pair_second + +#define ecs_set_override(world, entity, T, ...)\ + ecs_add_id(world, entity, ECS_OVERRIDE | ecs_id(T));\ + ecs_set(world, entity, T, __VA_ARGS__) + +#define ecs_emplace(world, entity, T)\ + (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T)))) + +#define ecs_emplace_pair(world, entity, First, second)\ + (ECS_CAST(First*, ecs_emplace_id(world, entity, ecs_pair_t(First, second)))) + +#define ecs_get(world, entity, T)\ + (ECS_CAST(const T*, ecs_get_id(world, entity, ecs_id(T)))) + +#define ecs_get_pair(world, subject, First, second)\ + (ECS_CAST(const First*, ecs_get_id(world, subject,\ + ecs_pair(ecs_id(First), second)))) + +#define ecs_get_pair_second(world, subject, first, Second)\ + (ECS_CAST(const Second*, ecs_get_id(world, subject,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_get_pair_object ecs_get_pair_second + +#define ecs_record_get(world, record, T)\ + (ECS_CAST(const T*, ecs_record_get_id(world, record, ecs_id(T)))) + +#define ecs_record_has(world, record, T)\ + (ecs_record_has_id(world, record, ecs_id(T))) + +#define ecs_record_get_pair(world, record, First, second)\ + (ECS_CAST(const First*, ecs_record_get_id(world, record, \ + ecs_pair(ecs_id(First), second)))) + +#define ecs_record_get_pair_second(world, record, first, Second)\ + (ECS_CAST(const Second*, ecs_record_get_id(world, record,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_record_get_mut(world, record, T)\ + (ECS_CAST(T*, ecs_record_get_mut_id(world, record, ecs_id(T)))) + +#define ecs_record_get_mut_pair(world, record, First, second)\ + (ECS_CAST(First*, ecs_record_get_mut_id(world, record, \ + ecs_pair(ecs_id(First), second)))) + +#define ecs_record_get_mut_pair_second(world, record, first, Second)\ + (ECS_CAST(Second*, ecs_record_get_mut_id(world, record,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_record_get_mut_pair_object ecs_record_get_mut_pair_second + +#define ecs_ref_init(world, entity, T)\ + ecs_ref_init_id(world, entity, ecs_id(T)) + +#define ecs_ref_get(world, ref, T)\ + (ECS_CAST(const T*, ecs_ref_get_id(world, ref, ecs_id(T)))) + +#define ecs_get_mut(world, entity, T)\ + (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T)))) + +#define ecs_get_mut_pair(world, subject, First, second)\ + (ECS_CAST(First*, ecs_get_mut_id(world, subject,\ + ecs_pair(ecs_id(First), second)))) + +#define ecs_get_mut_pair_second(world, subject, first, Second)\ + (ECS_CAST(Second*, ecs_get_mut_id(world, subject,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_get_mut_pair_object ecs_get_mut_pair_second + +#define ecs_modified(world, entity, component)\ + ecs_modified_id(world, entity, ecs_id(component)) + +#define ecs_modified_pair(world, subject, first, second)\ + ecs_modified_id(world, subject, ecs_pair(first, second)) + +/** @} */ + +/** + * @defgroup flecs_c_singletons Singletons + * @{ + */ + +#define ecs_singleton_add(world, comp)\ + ecs_add(world, ecs_id(comp), comp) + +#define ecs_singleton_remove(world, comp)\ + ecs_remove(world, ecs_id(comp), comp) + +#define ecs_singleton_get(world, comp)\ + ecs_get(world, ecs_id(comp), comp) + +#define ecs_singleton_set_ptr(world, comp, ptr)\ + ecs_set_ptr(world, ecs_id(comp), comp, ptr) + +#define ecs_singleton_set(world, comp, ...)\ + ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) + +#define ecs_singleton_get_mut(world, comp)\ + ecs_get_mut(world, ecs_id(comp), comp) + +#define ecs_singleton_modified(world, comp)\ + ecs_modified(world, ecs_id(comp), comp) + +/** @} */ + +/** + * @defgroup flecs_c_has Has, Owns, Shares + * @{ + */ + +#define ecs_has(world, entity, T)\ + ecs_has_id(world, entity, ecs_id(T)) + +#define ecs_has_pair(world, entity, first, second)\ + ecs_has_id(world, entity, ecs_pair(first, second)) + +#define ecs_owns_pair(world, entity, first, second)\ + ecs_owns_id(world, entity, ecs_pair(first, second)) + +#define ecs_owns(world, entity, T)\ + ecs_owns_id(world, entity, ecs_id(T)) + +#define ecs_shares_id(world, entity, id)\ + (ecs_search_relation(world, ecs_get_table(world, entity), 0, ecs_id(id), \ + EcsIsA, 1, 0, 0, 0, 0) != -1) + +#define ecs_shares_pair(world, entity, first, second)\ + (ecs_shares_id(world, entity, ecs_pair(first, second))) + +#define ecs_shares(world, entity, T)\ + (ecs_shares_id(world, entity, ecs_id(T))) + +/** @} */ + +/** + * @defgroup flecs_c_enable_disable Enabling & Disabling + * @{ + */ + +#define ecs_enable_component(world, entity, T, enable)\ + ecs_enable_id(world, entity, ecs_id(T), enable) + +#define ecs_is_enabled_component(world, entity, T)\ + ecs_is_enabled_id(world, entity, ecs_id(T)) + +#define ecs_enable_pair(world, entity, First, second, enable)\ + ecs_enable_id(world, entity, ecs_pair(ecs_id(First), second), enable) + +#define ecs_is_enabled_pair(world, entity, First, second)\ + ecs_is_enabled_id(world, entity, ecs_pair(ecs_id(First), second)) + +/** @} */ + +/** + * @defgroup flecs_c_entity_names Entity Names + * @{ + */ + +#define ecs_lookup_path(world, parent, path)\ + ecs_lookup_path_w_sep(world, parent, path, ".", NULL, true) + +#define ecs_lookup_fullpath(world, path)\ + ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true) + +#define ecs_get_path(world, parent, child)\ + ecs_get_path_w_sep(world, parent, child, ".", NULL) + +#define ecs_get_fullpath(world, child)\ + ecs_get_path_w_sep(world, 0, child, ".", NULL) + +#define ecs_get_fullpath_buf(world, child, buf)\ + ecs_get_path_w_sep_buf(world, 0, child, ".", NULL, buf) + +#define ecs_new_from_path(world, parent, path)\ + ecs_new_from_path_w_sep(world, parent, path, ".", NULL) + +#define ecs_new_from_fullpath(world, path)\ + ecs_new_from_path_w_sep(world, 0, path, ".", NULL) + +#define ecs_add_path(world, entity, parent, path)\ + ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) + +#define ecs_add_fullpath(world, entity, path)\ + ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) + +/** @} */ + +/** @} */ + +/** + * @defgroup flecs_c_components Component API + * @{ + */ + +#define ecs_set_hooks(world, T, ...)\ + ecs_set_hooks_id(world, ecs_id(T), &(ecs_type_hooks_t)__VA_ARGS__) + +#define ecs_get_hooks(world, T)\ + ecs_get_hooks_id(world, ecs_id(T)); + +/** Declare a constructor. + * Example: + * + * @code + * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); + * @endcode + */ +#define ECS_CTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) + +/** Declare a destructor. + * Example: + * + * @code + * ECS_DTOR(MyType, ptr, { free(ptr->value); }); + * @endcode + */ +#define ECS_DTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, dtor, var, __VA_ARGS__) + +/** Declare a copy action. + * Example: + * + * @code + * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); + * @endcode + */ +#define ECS_COPY(type, dst_var, src_var, ...)\ + ECS_COPY_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare a move action. + * Example: + * + * @code + * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); + * @endcode + */ +#define ECS_MOVE(type, dst_var, src_var, ...)\ + ECS_MOVE_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare component hooks. + * Example: + * + * @code + * ECS_ON_SET(MyType, ptr, { printf("%d\n", ptr->value); }); + * @endcode + */ +#define ECS_ON_ADD(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_add(type), ptr, __VA_ARGS__) +#define ECS_ON_REMOVE(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_remove(type), ptr, __VA_ARGS__) +#define ECS_ON_SET(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_set(type), ptr, __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 +#define ecs_on_set(type) type##_on_set +#define ecs_on_add(type) type##_on_add +#define ecs_on_remove(type) type##_on_remove + +/** @} */ + +/** + * @defgroup flecs_c_ids Id API + * @{ + */ + +#define ecs_count(world, type)\ + ecs_count_id(world, ecs_id(type)) + +/** @} */ + +/** + * @defgroup flecs_c_iterators Iterator API + * @{ + */ + +#define ecs_field(it, T, index)\ + (ECS_CAST(T*, ecs_field_w_size(it, sizeof(T), index))) + +/** @} */ + +/** + * @defgroup flecs_c_tables Table API + * @{ + */ + +#define ecs_table_get(world, table, T, offset)\ + (ECS_CAST(T*, ecs_table_get_id(world, table, ecs_id(T), offset))) + +#define ecs_table_get_pair(world, table, First, second, offset)\ + (ECS_CAST(First*, ecs_table_get_id(world, table, ecs_pair(ecs_id(First), second), offset))) + +#define ecs_table_get_pair_second(world, table, first, Second, offset)\ + (ECS_CAST(Second*, ecs_table_get_id(world, table, ecs_pair(first, ecs_id(Second)), offset))) + +/** @} */ + +/** + * @defgroup flecs_c_values Value API + * @{ + */ + +#define ecs_value(T, ptr) ((ecs_value_t){ecs_id(T), ptr}) +#define ecs_value_new_t(world, T) ecs_value_new(world, ecs_id(T)) + +/** @} */ + +/** @} */ + +/** + * @defgroup flecs_c_table_sorting Table sorting + * Convenience macro's for sorting tables. + * + * @{ + */ +#define ecs_sort_table(id) ecs_id(id##_sort_table) + +#define ecs_compare(id) ecs_id(id##_compare_fn) + +/* Declare efficient table sorting operation that uses provided compare function. + * For best results use LTO or make the function body visible in the same compilation unit. + * Variadic arguments are prepended before generated functions, use it to declare static + * or exported functions. + * Parameters of the comparison function: + * ecs_entity_t e1, const void* ptr1, + * ecs_entity_t e2, const void* ptr2 + * Parameters of the sort functions: + * ecs_world_t *world + * ecs_table_t *table + * ecs_entity_t *entities + * void *ptr + * int32_t elem_size + * int32_t lo + * int32_t hi + * ecs_order_by_action_t order_by - Pointer to the original comparison function. You are not supposed to use it. + * Example: + * + * @code + * int CompareMyType(ecs_entity_t e1, const void* ptr1, ecs_entity_t e2, const void* ptr2) { const MyType* p1 = ptr1; const MyType* p2 = ptr2; return p1->value - p2->value; } + * ECS_SORT_TABLE_WITH_COMPARE(MyType, MyCustomCompare, CompareMyType) + * @endcode + */ +#define ECS_SORT_TABLE_WITH_COMPARE(id, op_name, compare_fn, ...) \ + static int32_t ECS_CONCAT(op_name, _partition)( \ + ecs_world_t *world, \ + ecs_table_t *table, \ + ecs_entity_t *entities, \ + void *ptr, \ + int32_t elem_size, \ + int32_t lo, \ + int32_t hi, \ + ecs_order_by_action_t order_by) \ + { \ + (void)(order_by); \ + int32_t p = (hi + lo) / 2; \ + void *pivot = ECS_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 = ECS_ELEM(ptr, elem_size, i); \ + } while ( compare_fn(entities[i], el, pivot_e, pivot) < 0); \ + do { \ + j --; \ + el = ECS_ELEM(ptr, elem_size, j); \ + } while ( compare_fn(entities[j], el, pivot_e, pivot) > 0); \ + if (i >= j) { \ + return j; \ + } \ + ecs_table_swap_rows(world, table, i, j); \ + if (p == i) { \ + pivot = ECS_ELEM(ptr, elem_size, j); \ + pivot_e = entities[j]; \ + } else if (p == j) { \ + pivot = ECS_ELEM(ptr, elem_size, i); \ + pivot_e = entities[i]; \ + } \ + goto repeat; \ + } \ + } \ + __VA_ARGS__ void op_name( \ + ecs_world_t *world, \ + ecs_table_t *table, \ + ecs_entity_t *entities, \ + void *ptr, \ + int32_t size, \ + int32_t lo, \ + int32_t hi, \ + ecs_order_by_action_t order_by) \ + { \ + if ((hi - lo) < 1) { \ + return; \ + } \ + int32_t p = ECS_CONCAT(op_name, _partition)(world, table, entities, ptr, size, lo, hi, order_by); \ + op_name(world, table, entities, ptr, size, lo, p, order_by); \ + op_name(world, table, entities, ptr, size, p + 1, hi, order_by); \ + } + +/* Declare efficient table sorting operation that uses default component comparison operator. + * For best results use LTO or make the comparison operator visible in the same compilation unit. + * Variadic arguments are prepended before generated functions, use it to declare static + * or exported functions. + * Example: + * + * @code + * ECS_COMPARE(MyType, { const MyType* p1 = ptr1; const MyType* p2 = ptr2; return p1->value - p2->value; }); + * ECS_SORT_TABLE(MyType) + * @endcode + */ +#define ECS_SORT_TABLE(id, ...) \ + ECS_SORT_TABLE_WITH_COMPARE(id, ecs_sort_table(id), ecs_compare(id), __VA_ARGS__) + +/* Declare component comparison operations. + * Parameters: + * ecs_entity_t e1, const void* ptr1, + * ecs_entity_t e2, const void* ptr2 + * Example: + * + * @code + * ECS_COMPARE(MyType, { const MyType* p1 = ptr1; const MyType* p2 = ptr2; return p1->value - p2->value; }); + * @endcode + */ +#define ECS_COMPARE(id, ...) \ + int ecs_compare(id)(ecs_entity_t e1, const void* ptr1, ecs_entity_t e2, const void* ptr2) { \ + __VA_ARGS__ \ + } + +/** @} */ + +/** + * @defgroup flecs_c_misc Misc + * Misc convenience macro's. + * + * @{ + */ + +#define ecs_isa(e) ecs_pair(EcsIsA, e) +#define ecs_childof(e) ecs_pair(EcsChildOf, e) +#define ecs_dependson(e) ecs_pair(EcsDependsOn, e) + +#define ecs_query_new(world, q_expr)\ + ecs_query_init(world, &(ecs_query_desc_t){\ + .filter.expr = q_expr\ + }) + +#define ecs_rule_new(world, q_expr)\ + ecs_rule_init(world, &(ecs_filter_desc_t){\ + .expr = q_expr\ + }) + +/** @} */ + +/** @} */ + +#endif // FLECS_C_ + + +#ifdef __cplusplus +} +#endif + +/** + * @file addons.h + * @brief Include enabled addons. + * + * This file should only be included by the main flecs.h header. + */ + +#ifndef FLECS_ADDONS_H +#define FLECS_ADDONS_H + +/* Blacklist macros */ +#ifdef FLECS_NO_CPP +#undef FLECS_CPP +#endif +#ifdef FLECS_NO_MODULE +#undef FLECS_MODULE +#endif +#ifdef FLECS_NO_PARSER +#undef FLECS_PARSER +#endif +#ifdef FLECS_NO_PLECS +#undef FLECS_PLECS +#endif +#ifdef FLECS_NO_RULES +#undef FLECS_RULES +#endif +#ifdef FLECS_NO_SNAPSHOT +#undef FLECS_SNAPSHOT +#endif +#ifdef FLECS_NO_MONITOR +#undef FLECS_MONITOR +#endif +#ifdef FLECS_NO_STATS +#undef FLECS_STATS +#endif +#ifdef FLECS_NO_SYSTEM +#undef FLECS_SYSTEM +#endif +#ifdef FLECS_NO_PIPELINE +#undef FLECS_PIPELINE +#endif +#ifdef FLECS_NO_TIMER +#undef FLECS_TIMER +#endif +#ifdef FLECS_NO_META +#undef FLECS_META +#endif +#ifdef FLECS_NO_META_C +#undef FLECS_META_C +#endif +#ifdef FLECS_NO_UNITS +#undef FLECS_UNITS +#endif +#ifdef FLECS_NO_EXPR +#undef FLECS_EXPR +#endif +#ifdef FLECS_NO_JSON +#undef FLECS_JSON +#endif +#ifdef FLECS_NO_DOC +#undef FLECS_DOC +#endif +#ifdef FLECS_NO_COREDOC +#undef FLECS_COREDOC +#endif +#ifdef FLECS_NO_LOG +#undef FLECS_LOG +#endif +#ifdef FLECS_NO_APP +#undef FLECS_APP +#endif +#ifdef FLECS_NO_OS_API_IMPL +#undef FLECS_OS_API_IMPL +#endif +#ifdef FLECS_NO_HTTP +#undef FLECS_HTTP +#endif +#ifdef FLECS_NO_REST +#undef FLECS_REST +#endif +#ifdef FLECS_NO_JOURNAL +#undef FLECS_JOURNAL +#endif + +/* Always included, if disabled functions are replaced with dummy macros */ +/** + * @file addons/journal.h + * @brief Journaling addon that logs API functions. + * + * The journaling addon traces API calls. The trace is formatted as runnable + * C code, which allows for (partially) reproducing the behavior of an app + * with the journaling trace. + * + * The journaling addon is disabled by default. Enabling it can have a + * significant impact on performance. + */ + +#ifdef FLECS_JOURNAL + +#ifndef FLECS_LOG +#define FLECS_LOG +#endif + +#ifndef FLECS_JOURNAL_H +#define FLECS_JOURNAL_H + +/** + * @defgroup c_addons_journal Journal + * @ingroup c_addons + * Journaling addon (disabled by default). + * + * + * @{ + */ + +/* Trace when log level is at or higher than level */ +#define FLECS_JOURNAL_LOG_LEVEL (0) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Journaling API, meant to be used by internals. */ + +typedef enum ecs_journal_kind_t { + EcsJournalNew, + EcsJournalMove, + EcsJournalClear, + EcsJournalDelete, + EcsJournalDeleteWith, + EcsJournalRemoveAll, + EcsJournalTableEvents +} ecs_journal_kind_t; + +FLECS_DBG_API +void flecs_journal_begin( + ecs_world_t *world, + ecs_journal_kind_t kind, + ecs_entity_t entity, + ecs_type_t *add, + ecs_type_t *remove); + +FLECS_DBG_API +void flecs_journal_end(void); + +#define flecs_journal(...)\ + flecs_journal_begin(__VA_ARGS__);\ + flecs_journal_end(); + +#ifdef __cplusplus +} +#endif // __cplusplus +/** @} */ +#endif // FLECS_JOURNAL_H +#else +#define flecs_journal_begin(...) +#define flecs_journal_end(...) +#define flecs_journal(...) + +#endif // FLECS_JOURNAL + +/** + * @file addons/log.h + * @brief Logging addon. + * + * The logging addon provides an API for (debug) tracing and reporting errors + * at various levels. When enabled, the logging addon can provide more detailed + * information about the state of the ECS and any errors that may occur. + * + * The logging addon can be disabled to reduce footprint of the library, but + * limits information logged to only file, line and error code. + * + * When enabled the logging addon can be configured to exclude levels of tracing + * from the build to reduce the impact on performance. By default all debug + * tracing is enabled for debug builds, tracing is enabled at release builds. + * + * Applications can change the logging level at runtime with ecs_log_set_level(), + * but what is actually logged depends on what is compiled (when compiled + * without debug tracing, setting the runtime level to debug won't have an + * effect). + * + * The logging addon uses the OS API log_ function for all tracing. + * + * Note that even when the logging addon is not enabled, its header/source must + * be included in a build. To prevent unused variable warnings in the code, some + * API functions are included when the addon is disabled, but have empty bodies. + */ + +#ifndef FLECS_LOG_H +#define FLECS_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef FLECS_LOG + +/** + * @defgroup c_addons_log Log + * @ingroup c_addons + * Logging functions. + * + * @{ + */ + +//////////////////////////////////////////////////////////////////////////////// +//// Tracing +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void ecs_deprecated_( + const char *file, + int32_t line, + const char *msg); + +/** Increase log stack. + * This operation increases the indent_ value of the OS API and can be useful to + * make nested behavior more visible. + * + * @param level The log level. + */ +FLECS_API +void ecs_log_push_(int32_t level); + +/** Decrease log stack. + * This operation decreases the indent_ value of the OS API and can be useful to + * make nested behavior more visible. + * + * @param level The log level. + */ +FLECS_API +void ecs_log_pop_(int32_t level); + +/** Should current level be logged. + * This operation returns true when the specified log level should be logged + * with the current log level. + * + * @param level The log level to check for. + * @return Whether logging is enabled for the current level. + */ +FLECS_API +bool ecs_should_log(int32_t level); + +//////////////////////////////////////////////////////////////////////////////// +//// Error reporting +//////////////////////////////////////////////////////////////////////////////// + +/** Get description for error code */ +FLECS_API +const char* ecs_strerror( + int32_t error_code); + +#else // FLECS_LOG + +//////////////////////////////////////////////////////////////////////////////// +//// Dummy macros for when logging is disabled +//////////////////////////////////////////////////////////////////////////////// + +#define ecs_deprecated_(file, line, msg)\ + (void)file;\ + (void)line;\ + (void)msg + +#define ecs_log_push_(level) +#define ecs_log_pop_(level) +#define ecs_should_log(level) false + +#define ecs_strerror(error_code)\ + (void)error_code + +#endif // FLECS_LOG + + +//////////////////////////////////////////////////////////////////////////////// +//// Logging functions (do nothing when logging is enabled) +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void ecs_print_( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void ecs_printv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args); + +FLECS_API +void ecs_log_( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void ecs_logv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args); + +FLECS_API +void ecs_abort_( + int32_t error_code, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void ecs_assert_log_( + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void ecs_parser_error_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...); + +FLECS_API +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args); + + +//////////////////////////////////////////////////////////////////////////////// +//// Logging macros +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY /* C89 doesn't support variadic macros */ + +/* Base logging function. Accepts a custom level */ +#define ecs_print(level, ...)\ + ecs_print_(level, __FILE__, __LINE__, __VA_ARGS__) + +#define ecs_printv(level, fmt, args)\ + ecs_printv_(level, __FILE__, __LINE__, fmt, args) + +#define ecs_log(level, ...)\ + ecs_log_(level, __FILE__, __LINE__, __VA_ARGS__) + +#define ecs_logv(level, fmt, args)\ + ecs_logv_(level, __FILE__, __LINE__, fmt, args) + +/* Tracing. Used for logging of infrequent events */ +#define ecs_trace_(file, line, ...) ecs_log_(0, file, line, __VA_ARGS__) +#define ecs_trace(...) ecs_trace_(__FILE__, __LINE__, __VA_ARGS__) + +/* Warning. Used when an issue occurs, but operation is successful */ +#define ecs_warn_(file, line, ...) ecs_log_(-2, file, line, __VA_ARGS__) +#define ecs_warn(...) ecs_warn_(__FILE__, __LINE__, __VA_ARGS__) + +/* Error. Used when an issue occurs, and operation failed. */ +#define ecs_err_(file, line, ...) ecs_log_(-3, file, line, __VA_ARGS__) +#define ecs_err(...) ecs_err_(__FILE__, __LINE__, __VA_ARGS__) + +/* Fatal. Used when an issue occurs, and the application cannot continue. */ +#define ecs_fatal_(file, line, ...) ecs_log_(-4, file, line, __VA_ARGS__) +#define ecs_fatal(...) ecs_fatal_(__FILE__, __LINE__, __VA_ARGS__) + +/* Optionally include warnings about using deprecated features */ +#ifndef FLECS_NO_DEPRECATED_WARNINGS +#define ecs_deprecated(...)\ + ecs_deprecated_(__FILE__, __LINE__, __VA_ARGS__) +#else +#define ecs_deprecated(...) +#endif // FLECS_NO_DEPRECATED_WARNINGS + +/* If no tracing verbosity is defined, pick default based on build config */ +#if !(defined(FLECS_LOG_0) || defined(FLECS_LOG_1) || defined(FLECS_LOG_2) || defined(FLECS_LOG_3)) +#if !defined(FLECS_NDEBUG) +#define FLECS_LOG_3 /* Enable all tracing in debug mode. May slow things down */ +#else +#define FLECS_LOG_0 /* Only enable infrequent tracing in release mode */ +#endif // !defined(FLECS_NDEBUG) +#endif // !(defined(FLECS_LOG_0) || defined(FLECS_LOG_1) || defined(FLECS_LOG_2) || defined(FLECS_LOG_3)) + + +/* Define/undefine macros based on compiled-in tracing level. This can optimize + * out tracing statements from a build, which improves performance. */ + +#if defined(FLECS_LOG_3) /* All debug tracing enabled */ +#define ecs_dbg_1(...) ecs_log(1, __VA_ARGS__); +#define ecs_dbg_2(...) ecs_log(2, __VA_ARGS__); +#define ecs_dbg_3(...) ecs_log(3, __VA_ARGS__); + +#define ecs_log_push_1() ecs_log_push_(1); +#define ecs_log_push_2() ecs_log_push_(2); +#define ecs_log_push_3() ecs_log_push_(3); + +#define ecs_log_pop_1() ecs_log_pop_(1); +#define ecs_log_pop_2() ecs_log_pop_(2); +#define ecs_log_pop_3() ecs_log_pop_(3); + +#define ecs_should_log_1() ecs_should_log(1) +#define ecs_should_log_2() ecs_should_log(2) +#define ecs_should_log_3() ecs_should_log(3) + +#define FLECS_LOG_2 +#define FLECS_LOG_1 +#define FLECS_LOG_0 + +#elif defined(FLECS_LOG_2) /* Level 2 and below debug tracing enabled */ +#define ecs_dbg_1(...) ecs_log(1, __VA_ARGS__); +#define ecs_dbg_2(...) ecs_log(2, __VA_ARGS__); +#define ecs_dbg_3(...) + +#define ecs_log_push_1() ecs_log_push_(1); +#define ecs_log_push_2() ecs_log_push_(2); +#define ecs_log_push_3() + +#define ecs_log_pop_1() ecs_log_pop_(1); +#define ecs_log_pop_2() ecs_log_pop_(2); +#define ecs_log_pop_3() + +#define ecs_should_log_1() ecs_should_log(1) +#define ecs_should_log_2() ecs_should_log(2) +#define ecs_should_log_3() false + +#define FLECS_LOG_1 +#define FLECS_LOG_0 + +#elif defined(FLECS_LOG_1) /* Level 1 debug tracing enabled */ +#define ecs_dbg_1(...) ecs_log(1, __VA_ARGS__); +#define ecs_dbg_2(...) +#define ecs_dbg_3(...) + +#define ecs_log_push_1() ecs_log_push_(1); +#define ecs_log_push_2() +#define ecs_log_push_3() + +#define ecs_log_pop_1() ecs_log_pop_(1); +#define ecs_log_pop_2() +#define ecs_log_pop_3() + +#define ecs_should_log_1() ecs_should_log(1) +#define ecs_should_log_2() false +#define ecs_should_log_3() false + +#define FLECS_LOG_0 + +#elif defined(FLECS_LOG_0) /* No debug tracing enabled */ +#define ecs_dbg_1(...) +#define ecs_dbg_2(...) +#define ecs_dbg_3(...) + +#define ecs_log_push_1() +#define ecs_log_push_2() +#define ecs_log_push_3() + +#define ecs_log_pop_1() +#define ecs_log_pop_2() +#define ecs_log_pop_3() + +#define ecs_should_log_1() false +#define ecs_should_log_2() false +#define ecs_should_log_3() false + +#else /* No tracing enabled */ +#undef ecs_trace +#define ecs_trace(...) +#define ecs_dbg_1(...) +#define ecs_dbg_2(...) +#define ecs_dbg_3(...) + +#define ecs_log_push_1() +#define ecs_log_push_2() +#define ecs_log_push_3() + +#define ecs_log_pop_1() +#define ecs_log_pop_2() +#define ecs_log_pop_3() + +#endif // defined(FLECS_LOG_3) + +/* Default debug tracing is at level 1 */ +#define ecs_dbg ecs_dbg_1 + +/* Default level for push/pop is 0 */ +#define ecs_log_push() ecs_log_push_(0) +#define ecs_log_pop() ecs_log_pop_(0) + +/** Abort. + * Unconditionally aborts process. */ +#define ecs_abort(error_code, ...)\ + ecs_abort_(error_code, __FILE__, __LINE__, __VA_ARGS__);\ + ecs_os_abort(); abort(); /* satisfy compiler/static analyzers */ + +/** Assert. + * Aborts if condition is false, disabled in debug mode. */ +#if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) +#define ecs_assert(condition, error_code, ...) +#else +#define ecs_assert(condition, error_code, ...)\ + if (!(condition)) {\ + ecs_assert_log_(error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ + ecs_os_abort();\ + }\ + assert(condition) /* satisfy compiler/static analyzers */ +#endif // FLECS_NDEBUG + +#define ecs_assert_var(var, error_code, ...)\ + ecs_assert(var, error_code, __VA_ARGS__);\ + (void)var + +/** Debug assert. + * Assert that is only valid in debug mode (ignores FLECS_KEEP_ASSERT) */ +#ifndef FLECS_NDEBUG +#define ecs_dbg_assert(condition, error_code, ...) ecs_assert(condition, error_code, __VA_ARGS__) +#else +#define ecs_dbg_assert(condition, error_code, ...) +#endif + +/** Sanitize assert. + * Assert that is only valid in sanitized mode (ignores FLECS_KEEP_ASSERT) */ +#ifdef FLECS_SANITIZE +#define ecs_san_assert(condition, error_code, ...) ecs_assert(condition, error_code, __VA_ARGS__) +#else +#define ecs_san_assert(condition, error_code, ...) +#endif + + +/* Silence dead code/unused label warnings when compiling without checks. */ +#define ecs_dummy_check\ + if ((false)) {\ + goto error;\ + } + +/** Check. + * goto error if condition is false. */ +#if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) +#define ecs_check(condition, error_code, ...) ecs_dummy_check +#else +#ifdef FLECS_SOFT_ASSERT +#define ecs_check(condition, error_code, ...)\ + if (!(condition)) {\ + ecs_assert_log_(error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ + goto error;\ + } +#else // FLECS_SOFT_ASSERT +#define ecs_check(condition, error_code, ...)\ + ecs_assert(condition, error_code, __VA_ARGS__);\ + ecs_dummy_check +#endif +#endif // FLECS_NDEBUG + +/** Panic. + * goto error when FLECS_SOFT_ASSERT is defined, otherwise abort */ +#if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) +#define ecs_throw(error_code, ...) ecs_dummy_check +#else +#ifdef FLECS_SOFT_ASSERT +#define ecs_throw(error_code, ...)\ + ecs_abort_(error_code, __FILE__, __LINE__, __VA_ARGS__);\ + goto error; +#else +#define ecs_throw(error_code, ...)\ + ecs_abort(error_code, __VA_ARGS__);\ + ecs_dummy_check +#endif +#endif // FLECS_NDEBUG + +/** Parser error */ +#define ecs_parser_error(name, expr, column, ...)\ + ecs_parser_error_(name, expr, column, __VA_ARGS__) + +#define ecs_parser_errorv(name, expr, column, fmt, args)\ + ecs_parser_errorv_(name, expr, column, fmt, args) + +#endif // FLECS_LEGACY + + +//////////////////////////////////////////////////////////////////////////////// +//// Functions that are always available +//////////////////////////////////////////////////////////////////////////////// + +/** Enable or disable log. + * This will enable builtin log. For log to work, it will have to be + * compiled in which requires defining one of the following macros: + * + * FLECS_LOG_0 - All log is disabled + * FLECS_LOG_1 - Enable log level 1 + * FLECS_LOG_2 - Enable log level 2 and below + * FLECS_LOG_3 - Enable log level 3 and below + * + * If no log level is defined and this is a debug build, FLECS_LOG_3 will + * have been automatically defined. + * + * The provided level corresponds with the log level. If -1 is provided as + * value, warnings are disabled. If -2 is provided, errors are disabled as well. + * + * @param level Desired tracing level. + * @return Previous log level. + */ +FLECS_API +int ecs_log_set_level( + int level); + +/** Get current log level. + * + * @return Previous log level. + */ +FLECS_API +int ecs_log_get_level(void); + +/** Enable/disable tracing with colors. + * By default colors are enabled. + * + * @param enabled Whether to enable tracing with colors. + * @return Previous color setting. + */ +FLECS_API +bool ecs_log_enable_colors( + bool enabled); + +/** Enable/disable logging timestamp. + * By default timestamps are disabled. Note that enabling timestamps introduces + * overhead as the logging code will need to obtain the current time. + * + * @param enabled Whether to enable tracing with timestamps. + * @return Previous timestamp setting. + */ +FLECS_API +bool ecs_log_enable_timestamp( + bool enabled); + +/** Enable/disable logging time since last log. + * By default deltatime is disabled. Note that enabling timestamps introduces + * overhead as the logging code will need to obtain the current time. + * + * When enabled, this logs the amount of time in seconds passed since the last + * log, when this amount is non-zero. The format is a '+' character followed by + * the number of seconds: + * + * +1 trace: log message + * + * @param enabled Whether to enable tracing with timestamps. + * @return Previous timestamp setting. + */ +FLECS_API +bool ecs_log_enable_timedelta( + bool enabled); + +/** Get last logged error code. + * Calling this operation resets the error code. + * + * @return Last error, 0 if none was logged since last call to last_error. + */ +FLECS_API +int ecs_log_last_error(void); + + +//////////////////////////////////////////////////////////////////////////////// +//// Error codes +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_INVALID_OPERATION (1) +#define ECS_INVALID_PARAMETER (2) +#define ECS_CONSTRAINT_VIOLATED (3) +#define ECS_OUT_OF_MEMORY (4) +#define ECS_OUT_OF_RANGE (5) +#define ECS_UNSUPPORTED (6) +#define ECS_INTERNAL_ERROR (7) +#define ECS_ALREADY_DEFINED (8) +#define ECS_MISSING_OS_API (9) +#define ECS_OPERATION_FAILED (10) +#define ECS_INVALID_CONVERSION (11) +#define ECS_ID_IN_USE (12) +#define ECS_CYCLE_DETECTED (13) +#define ECS_LEAK_DETECTED (14) +#define ECS_DOUBLE_FREE (15) + +#define ECS_INCONSISTENT_NAME (20) +#define ECS_NAME_IN_USE (21) +#define ECS_NOT_A_COMPONENT (22) +#define ECS_INVALID_COMPONENT_SIZE (23) +#define ECS_INVALID_COMPONENT_ALIGNMENT (24) +#define ECS_COMPONENT_NOT_REGISTERED (25) +#define ECS_INCONSISTENT_COMPONENT_ID (26) +#define ECS_INCONSISTENT_COMPONENT_ACTION (27) +#define ECS_MODULE_UNDEFINED (28) +#define ECS_MISSING_SYMBOL (29) +#define ECS_ALREADY_IN_USE (30) + +#define ECS_ACCESS_VIOLATION (40) +#define ECS_COLUMN_INDEX_OUT_OF_RANGE (41) +#define ECS_COLUMN_IS_NOT_SHARED (42) +#define ECS_COLUMN_IS_SHARED (43) +#define ECS_COLUMN_TYPE_MISMATCH (45) + +#define ECS_INVALID_WHILE_READONLY (70) +#define ECS_LOCKED_STORAGE (71) +#define ECS_INVALID_FROM_WORKER (72) + + +//////////////////////////////////////////////////////////////////////////////// +//// Used when logging with colors is enabled +//////////////////////////////////////////////////////////////////////////////// + +#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" + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif // FLECS_LOG_H + + +/* Handle addon dependencies that need declarations to be visible in header */ +#ifdef FLECS_MONITOR +#ifndef FLECS_STATS +#define FLECS_STATS +#endif +#ifndef FLECS_SYSTEM +#define FLECS_SYSTEM +#endif +#ifndef FLECS_TIMER +#define FLECS_TIMER +#endif +#endif + +#ifdef FLECS_REST +#define FLECS_HTTP +#endif + +#ifdef FLECS_PLECS +#define FLECS_EXPR +#endif + +#ifdef FLECS_APP +#ifdef FLECS_NO_APP +#error "FLECS_NO_APP failed: APP is required by other addons" +#endif +/** + * @file addons/app.h + * @brief App addon. + * + * The app addon is a wrapper around the application's main loop. Its main + * purpose is to provide a hook to modules that need to take control of the + * main loop, as is for example the case with native applications that use + * emscripten with webGL. + */ + +#ifdef FLECS_APP + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifndef FLECS_APP_H +#define FLECS_APP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup c_addons_app App + * @ingroup c_addons + * Optional addon for running the main application loop. + * + * @{ + */ + +/** Callback type for init action. */ +typedef int(*ecs_app_init_action_t)( + ecs_world_t *world); + +/** Used with ecs_app_run(). */ +typedef struct ecs_app_desc_t { + ecs_ftime_t target_fps; /**< Target FPS. */ + ecs_ftime_t delta_time; /**< Frame time increment (0 for measured values) */ + int32_t threads; /**< Number of threads. */ + int32_t frames; /**< Number of frames to run (0 for infinite) */ + bool enable_rest; /**< Enables ECS access over HTTP, necessary for explorer */ + bool enable_monitor; /**< Periodically collect statistics */ + uint16_t port; /**< HTTP port used by REST API */ + + ecs_app_init_action_t init; /**< If set, function is ran before starting the + * main loop. */ + + void *ctx; /**< Reserved for custom run/frame actions */ +} ecs_app_desc_t; + +/** Callback type for run action. */ +typedef int(*ecs_app_run_action_t)( + ecs_world_t *world, + ecs_app_desc_t *desc); + +/** Callback type for frame action. */ +typedef int(*ecs_app_frame_action_t)( + ecs_world_t *world, + const ecs_app_desc_t *desc); + +/** Run application. + * This will run the application with the parameters specified in desc. After + * the application quits (ecs_quit() is called) the world will be cleaned up. + * + * If a custom run action is set, it will be invoked by this operation. The + * default run action calls the frame action in a loop until it returns a + * non-zero value. + * + * @param world The world. + * @param desc Application parameters. + */ +FLECS_API +int ecs_app_run( + ecs_world_t *world, + ecs_app_desc_t *desc); + +/** Default frame callback. + * This operation will run a single frame. By default this operation will invoke + * ecs_progress() directly, unless a custom frame action is set. + * + * @param world The world. + * @param desc The desc struct passed to ecs_app_run(). + * @return value returned by ecs_progress() + */ +FLECS_API +int ecs_app_run_frame( + ecs_world_t *world, + const ecs_app_desc_t *desc); + +/** Set custom run action. + * See ecs_app_run(). + * + * @param callback The run action. + */ +FLECS_API +int ecs_app_set_run_action( + ecs_app_run_action_t callback); + +/** Set custom frame action. + * See ecs_app_run_frame(). + * + * @param callback The frame action. + */ +FLECS_API +int ecs_app_set_frame_action( + ecs_app_frame_action_t callback); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif // FLECS_APP + +#endif + +#ifdef FLECS_HTTP +#ifdef FLECS_NO_HTTP +#error "FLECS_NO_HTTP failed: HTTP is required by other addons" +#endif +/** + * @file addons/http.h + * @brief HTTP addon. + * + * Minimalistic HTTP server that can receive and reply to simple HTTP requests. + * The main goal of this addon is to enable remotely connecting to a running + * Flecs application (for example, with a web-based UI) and request/visualize + * data from the ECS world. + * + * Each server instance creates a single thread used for receiving requests. + * Receiving requests are enqueued and handled when the application calls + * ecs_http_server_dequeue(). This increases latency of request handling vs. + * responding directly in the receive thread, but is better suited for + * retrieving data from ECS applications, as requests can be processed by an ECS + * system without having to lock the world. + * + * This server is intended to be used in a development environment. + */ + +#ifdef FLECS_HTTP + +/** + * @defgroup c_addons_http Http + * @ingroup c_addons + * Simple HTTP server used for serving up REST API. + * + * @{ + */ + +#if !defined(FLECS_OS_API_IMPL) && !defined(FLECS_NO_OS_API_IMPL) +#define FLECS_OS_API_IMPL +#endif + +#ifndef FLECS_HTTP_H +#define FLECS_HTTP_H + +/* Maximum number of headers in request */ +#define ECS_HTTP_HEADER_COUNT_MAX (32) + +/* Maximum number of query parameters in request */ +#define ECS_HTTP_QUERY_PARAM_COUNT_MAX (32) + +#ifdef __cplusplus +extern "C" { +#endif + +/** HTTP server */ +typedef struct ecs_http_server_t ecs_http_server_t; + +/** A connection manages communication with the remote host */ +typedef struct { + uint64_t id; + ecs_http_server_t *server; + + char host[128]; + char port[16]; +} ecs_http_connection_t; + +/** Helper type used for headers & URL query parameters */ +typedef struct { + const char *key; + const char *value; +} ecs_http_key_value_t; + +/** Supported request methods */ +typedef enum { + EcsHttpGet, + EcsHttpPost, + EcsHttpPut, + EcsHttpDelete, + EcsHttpOptions, + EcsHttpMethodUnsupported +} ecs_http_method_t; + +/** A request */ +typedef struct { + uint64_t id; + + ecs_http_method_t method; + char *path; + char *body; + ecs_http_key_value_t headers[ECS_HTTP_HEADER_COUNT_MAX]; + ecs_http_key_value_t params[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_count; + int32_t param_count; + + ecs_http_connection_t *conn; +} ecs_http_request_t; + +/** A reply */ +typedef struct { + int code; /**< default = 200 */ + ecs_strbuf_t body; /**< default = "" */ + const char* status; /**< default = OK */ + const char* content_type; /**< default = application/json */ + ecs_strbuf_t headers; /**< default = "" */ +} ecs_http_reply_t; + +#define ECS_HTTP_REPLY_INIT \ + (ecs_http_reply_t){200, ECS_STRBUF_INIT, "OK", "application/json", ECS_STRBUF_INIT} + +/* Global statistics. */ +extern int64_t ecs_http_request_received_count; +extern int64_t ecs_http_request_invalid_count; +extern int64_t ecs_http_request_handled_ok_count; +extern int64_t ecs_http_request_handled_error_count; +extern int64_t ecs_http_request_not_handled_count; +extern int64_t ecs_http_request_preflight_count; +extern int64_t ecs_http_send_ok_count; +extern int64_t ecs_http_send_error_count; +extern int64_t ecs_http_busy_count; + +/** Request callback. + * Invoked for each valid request. The function should populate the reply and + * return true. When the function returns false, the server will reply with a + * 404 (Not found) code. */ +typedef bool (*ecs_http_reply_action_t)( + const ecs_http_request_t* request, + ecs_http_reply_t *reply, + void *ctx); + +/** Used with ecs_http_server_init(). */ +typedef struct { + ecs_http_reply_action_t callback; /**< Function called for each request */ + void *ctx; /**< Passed to callback (optional) */ + uint16_t port; /**< HTTP port */ + const char *ipaddr; /**< Interface to listen on (optional) */ + int32_t send_queue_wait_ms; /**< Send queue wait time when empty */ + ecs_ftime_t cache_timeout; /**< Cache invalidation timeout (0 disables caching) */ + ecs_ftime_t cache_purge_timeout; /**< Cache purge timeout (for purging cache entries) */ +} ecs_http_server_desc_t; + +/** Create server. + * Use ecs_http_server_start() to start receiving requests. + * + * @param desc Server configuration parameters. + * @return The new server, or NULL if creation failed. + */ +FLECS_API +ecs_http_server_t* ecs_http_server_init( + const ecs_http_server_desc_t *desc); + +/** Destroy server. + * This operation will stop the server if it was still running. + * + * @param server The server to destroy. + */ +FLECS_API +void ecs_http_server_fini( + ecs_http_server_t* server); + +/** Start server. + * After this operation the server will be able to accept requests. + * + * @param server The server to start. + * @return Zero if successful, non-zero if failed. + */ +FLECS_API +int ecs_http_server_start( + ecs_http_server_t* server); + +/** Process server requests. + * This operation invokes the reply callback for each received request. No new + * requests will be enqueued while processing requests. + * + * @param server The server for which to process requests. + */ +FLECS_API +void ecs_http_server_dequeue( + ecs_http_server_t* server, + ecs_ftime_t delta_time); + +/** Stop server. + * After this operation no new requests can be received. + * + * @param server The server. + */ +FLECS_API +void ecs_http_server_stop( + ecs_http_server_t* server); + +/** Emulate a request. + * The request string must be a valid HTTP request. A minimal example: + * + * GET /entity/flecs/core/World?label=true HTTP/1.1 + * + * @param srv The server. + * @param req The request. + * @param len The length of the request (optional). + * @return The reply. + */ +FLECS_API +int ecs_http_server_http_request( + ecs_http_server_t* srv, + const char *req, + ecs_size_t len, + ecs_http_reply_t *reply_out); + +/** Convenience wrapper around ecs_http_server_http_request(). */ +FLECS_API +int ecs_http_server_request( + ecs_http_server_t* srv, + const char *method, + const char *req, + ecs_http_reply_t *reply_out); + +/** Get context provided in ecs_http_server_desc_t */ +FLECS_API +void* ecs_http_server_ctx( + ecs_http_server_t* srv); + +/** Find header in request. + * + * @param req The request. + * @param name name of the header to find + * @return The header value, or NULL if not found. +*/ +FLECS_API +const char* ecs_http_get_header( + const ecs_http_request_t* req, + const char* name); + +/** Find query parameter in request. + * + * @param req The request. + * @param name The parameter name. + * @return The decoded parameter value, or NULL if not found. + */ +FLECS_API +const char* ecs_http_get_param( + const ecs_http_request_t* req, + const char* name); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif // FLECS_HTTP_H + +#endif // FLECS_HTTP + +#endif + +#ifdef FLECS_REST +#ifdef FLECS_NO_REST +#error "FLECS_NO_REST failed: REST is required by other addons" +#endif +/** + * @file addons/rest.h + * @brief REST API addon. + * + * A small REST API that uses the HTTP server and JSON serializer to provide + * access to application data for remote applications. + * + * A description of the API can be found in docs/RestApi.md + */ + +#ifdef FLECS_REST + +/** + * @defgroup c_addons_rest Rest + * @ingroup c_addons + * REST API for querying and mutating entities. + * + * @{ + */ + +/* Used for the HTTP server */ +#ifndef FLECS_HTTP +#define FLECS_HTTP +#endif + +/* Used for building the JSON replies */ +#ifndef FLECS_JSON +#define FLECS_JSON +#endif + +/* Query engine used */ +#ifndef FLECS_RULES +#define FLECS_RULES +#endif + +/* For the REST system */ +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifndef FLECS_REST_H +#define FLECS_REST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_REST_DEFAULT_PORT (27750) + +/** Component that instantiates the REST API */ +FLECS_API extern const ecs_entity_t ecs_id(EcsRest); + +typedef struct { + uint16_t port; /**< Port of server (optional, default = 27750) */ + char *ipaddr; /**< Interface address (optional, default = 0.0.0.0) */ + void *impl; +} EcsRest; + +/** Create HTTP server for REST API. + * This allows for the creation of a REST server that can be managed by the + * application without using Flecs systems. + * + * @param world The world. + * @param desc The HTTP server descriptor. + * @return The HTTP server, or NULL if failed. + */ +FLECS_API +ecs_http_server_t* ecs_rest_server_init( + ecs_world_t *world, + const ecs_http_server_desc_t *desc); + +/** Cleanup REST HTTP server. + * The server must have been created with ecs_rest_server_init(). + */ +FLECS_API +void ecs_rest_server_fini( + ecs_http_server_t *srv); + +/* Module import */ +FLECS_API +void FlecsRestImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_TIMER +#ifdef FLECS_NO_TIMER +#error "FLECS_NO_TIMER failed: TIMER is required by other addons" +#endif +/** + * @file addons/timer.h + * @brief Timer module. + * + * Timers can be used to trigger actions at periodic or one-shot intervals. They + * are typically used together with systems and pipelines. + */ + +#ifdef FLECS_TIMER + +/** + * @defgroup c_addons_timer Timer + * @ingroup c_addons + * Run systems at a time interval. + * + * @{ + */ + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifndef FLECS_TIMER_H +#define FLECS_TIMER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Component used for one shot/interval timer functionality */ +typedef struct EcsTimer { + ecs_ftime_t timeout; /**< Timer timeout period */ + ecs_ftime_t time; /**< Incrementing time value */ + ecs_ftime_t overshoot; /**< Used to correct returned interval time */ + 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; /**< Source of the rate filter */ + int32_t rate; /**< Rate of the rate filter */ + int32_t tick_count; /**< Number of times the rate filter ticked */ + ecs_ftime_t time_elapsed; /**< Time elapsed since last tick */ +} EcsRateFilter; + + +/** 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. The timer can be started and stopped with + * ecs_start_timer() and ecs_stop_timer(). + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * The tick_source entity will be a tick source after this operation. Tick + * sources can be read by getting the EcsTickSource component. If the tick + * source ticked this frame, the 'tick' member will be true. When the tick + * source is a system, the system will tick when the timer ticks. + * + * @param world The world. + * @param tick_source 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 tick_source, + ecs_ftime_t 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 EcsTimer 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. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * The tick_source entity will be a tick source after this operation. Tick + * sources can be read by getting the EcsTickSource component. If the tick + * source ticked this frame, the 'tick' member will be true. When the tick + * source is a system, the system will tick when the timer ticks. + * + * @param world The world. + * @param tick_source The timer. + * @return The current timeout value, or 0 if no timer is active. + */ +FLECS_API +ecs_ftime_t ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t tick_source); + +/** Set timer interval. + * This operation will continuously invoke systems associated with the timer + * after the interval period expires. If the entity contains an existing timer, + * the interval value will be reset. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * The tick_source entity will be a tick source after this operation. Tick + * sources can be read by getting the EcsTickSource component. If the tick + * source ticked this frame, the 'tick' member will be true. When the tick + * source is a system, the system will tick when the timer ticks. + * + * @param world The world. + * @param tick_source 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 tick_source, + ecs_ftime_t interval); + +/** Get current interval value for the specified timer. + * This operation returns the value set by ecs_set_interval(). If the entity is + * not a timer, the operation will return 0. + * + * @param world The world. + * @param tick_source The timer for which to set the interval. + * @return The current interval value, or 0 if no timer is active. + */ +FLECS_API +ecs_ftime_t ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t tick_source); + +/** Start timer. + * This operation resets the timer and starts it with the specified timeout. + * + * @param world The world. + * @param tick_source The timer to start. + */ +FLECS_API +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t tick_source); + +/** Stop timer + * This operation stops a timer from triggering. + * + * @param world The world. + * @param tick_source The timer to stop. + */ +FLECS_API +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t tick_source); + +/** Reset time value of timer to 0. + * This operation resets the timer value to 0. + * + * @param world The world. + * @param tick_source The timer to reset. + */ +FLECS_API +void ecs_reset_timer( + ecs_world_t *world, + ecs_entity_t tick_source); + +/** Enable randomizing initial time value of timers. + * Initializes timers with a random time value, which can improve scheduling as + * systems/timers for the same interval don't all happen on the same tick. + * + * @param world The world. + */ +FLECS_API +void ecs_randomize_timers( + ecs_world_t *world); + +/** Set rate filter. + * This operation initializes a rate filter. Rate filters sample tick sources + * and tick at a configurable multiple. A rate filter is a tick source itself, + * which means that rate filters can be chained. + * + * Rate filters enable deterministic system execution which cannot be achieved + * with interval timers alone. For example, if timer A has interval 2.0 and + * timer B has interval 4.0, it is not guaranteed that B will tick at exactly + * twice the multiple of A. This is partly due to the indeterministic nature of + * timers, and partly due to floating point rounding errors. + * + * Rate filters can be combined with timers (or other rate filters) to ensure + * that a system ticks at an exact multiple of a tick source (which can be + * another system). If a rate filter is created with a rate of 1 it will tick + * at the exact same time as its source. + * + * If no tick source is provided, the rate filter will use the frame tick as + * source, which corresponds with the number of times ecs_progress() is called. + * + * The tick_source entity will be a tick source after this operation. Tick + * sources can be read by getting the EcsTickSource component. If the tick + * source ticked this frame, the 'tick' member will be true. When the tick + * source is a system, the system will tick when the timer ticks. + * + * @param world The world. + * @param tick_source The rate 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( + ecs_world_t *world, + ecs_entity_t tick_source, + int32_t rate, + ecs_entity_t source); + +/** Assign tick source to system. + * Systems can be their own tick source, which can be any of the tick sources + * (one shot timers, interval times and rate filters). However, in some cases it + * is must be guaranteed that different systems tick on the exact same frame. + * + * This cannot be guaranteed by giving two systems the same interval/rate filter + * as it is possible that one system is (for example) disabled, which would + * cause the systems to go out of sync. To provide these guarantees, systems + * must use the same tick source, which is what this operation enables. + * + * When two systems share the same tick source, it is guaranteed that they tick + * in the same frame. The provided tick source can be any entity that is a tick + * source, including another system. If the provided entity is not a tick source + * the system will not be ran. + * + * To disassociate a tick source from a system, use 0 for the tick_source + * parameter. + * + * @param world The world. + * @param system The system to associate with the timer. + * @param tick_source The tick source 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 +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void FlecsTimerImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_PIPELINE +#ifdef FLECS_NO_PIPELINE +#error "FLECS_NO_PIPELINE failed: PIPELINE is required by other addons" +#endif +/** + * @file addons/pipeline.h + * @brief Pipeline module. + * + * The pipeline module provides support for running systems automatically and + * on multiple threads. A pipeline is a collection of tags that can be added to + * systems. When ran, a pipeline will query for all systems that have the tags + * that belong to a pipeline, and run them. + * + * The module defines a number of builtin tags (EcsPreUpdate, EcsOnUpdate, + * EcsPostUpdate etc.) that are registered with the builtin pipeline. The + * builtin pipeline is ran by default when calling ecs_progress(). An + * application can set a custom pipeline with the ecs_set_pipeline() function. + */ + +#ifdef FLECS_PIPELINE + +/** + * @defgroup c_addons_pipeline Pipeline + * @ingroup c_addons + * Pipelines order and schedule systems for execution. + * + * @{ + */ + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_SYSTEM +#define FLECS_SYSTEM +#endif + +#if !defined(FLECS_OS_API_IMPL) && !defined(FLECS_NO_OS_API_IMPL) +#define FLECS_OS_API_IMPL +#endif + +#ifndef FLECS_PIPELINE_H +#define FLECS_PIPELINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef FLECS_LEGACY + +#define ECS_PIPELINE_DEFINE(world, id_, ...) \ + { \ + ecs_pipeline_desc_t desc = {0}; \ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = id_;\ + edesc.name = #id_;\ + desc.entity = ecs_entity_init(world, &edesc);\ + desc.query.filter.expr = #__VA_ARGS__; \ + id_ = ecs_pipeline_init(world, &desc); \ + ecs_id(id_) = id_;\ + } \ + ecs_assert(id_ != 0, ECS_INVALID_PARAMETER, NULL); + +#define ECS_PIPELINE(world, id, ...) \ + ecs_entity_t id = 0, ecs_id(id) = 0; ECS_PIPELINE_DEFINE(world, id, __VA_ARGS__);\ + (void)id;\ + (void)ecs_id(id); + +#define ecs_pipeline(world, ...)\ + ecs_pipeline_init(world, &(ecs_pipeline_desc_t) __VA_ARGS__ ) + +#endif + +/* Pipeline descriptor (used with ecs_pipeline_init()) */ +typedef struct ecs_pipeline_desc_t { + /* Existing entity to associate with pipeline (optional) */ + ecs_entity_t entity; + + /* Query descriptor. The first term of the query must match the EcsSystem + * component. */ + ecs_query_desc_t query; +} ecs_pipeline_desc_t; + +/** Create a custom pipeline. + */ +FLECS_API +ecs_entity_t ecs_pipeline_init( + ecs_world_t *world, + const ecs_pipeline_desc_t *desc); + +/** 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. + * @return The current pipeline. + */ +FLECS_API +ecs_entity_t ecs_get_pipeline( + const 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, + ecs_ftime_t delta_time); + +/** 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, + ecs_ftime_t 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); + +/** Run pipeline. + * This will run all systems in the provided pipeline. This operation may be + * invoked from multiple threads, and only when staging is disabled, as the + * pipeline manages staging and, if necessary, synchronization between threads. + * + * If 0 is provided for the pipeline id, the default pipeline will be ran (this + * is either the builtin pipeline or the pipeline set with set_pipeline()). + * + * When using progress() this operation will be invoked automatically for the + * default pipeline (either the builtin pipeline or the pipeline set with + * set_pipeline()). An application may run additional pipelines. + * + * @param world The world. + * @param pipeline The pipeline to run. + */ +FLECS_API +void ecs_run_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_ftime_t delta_time); + + +//////////////////////////////////////////////////////////////////////////////// +//// 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. + * Calling ecs_set_threads() will also end the use of task threads setup with + * ecs_set_task_threads() and vice-versa */ +FLECS_API +void ecs_set_threads( + ecs_world_t *world, + int32_t threads); + +/** Set number of worker task threads. + * ecs_set_task_threads() is similar to ecs_set_threads(), except threads are treated + * as short-lived tasks and will be created and joined around each update of the world. + * Creation and joining of these tasks will use the os_api_t tasks APIs rather than the + * the standard thread API functions, although they may be the same if desired. + * This function is useful for multithreading world updates using an external + * asynchronous job system rather than long running threads by providing the APIs + * to create tasks for your job system and then wait on their conclusion. + * The operation may be called multiple times to reconfigure the number of task threads + * used, but never while running a system / pipeline. + * Calling ecs_set_task_threads() will also end the use of threads setup with + * ecs_set_threads() and vice-versa */ + +FLECS_API +void ecs_set_task_threads( + ecs_world_t *world, + int32_t task_threads); + +/** Returns true if task thread use have been requested. */ +FLECS_API +bool ecs_using_task_threads( + ecs_world_t *world); + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void FlecsPipelineImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_SYSTEM +#ifdef FLECS_NO_SYSTEM +#error "FLECS_NO_SYSTEM failed: SYSTEM is required by other addons" +#endif +/** + * @file addons/system.h + * @brief System module. + * + * The system module allows for creating and running systems. A system is a + * query in combination with a callback function. In addition systems have + * support for time management and can be monitored by the stats addon. + */ + +#ifdef FLECS_SYSTEM + +/** + * @defgroup c_addons_system System + * @ingroup c_addons + * Systems are a query + function that can be ran manually or by a pipeline. + * + * @{ + */ + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_SYSTEM_H +#define FLECS_SYSTEM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Component used to provide a tick source to systems */ +typedef struct EcsTickSource { + bool tick; /**< True if providing tick */ + ecs_ftime_t time_elapsed; /**< Time elapsed since last tick */ +} EcsTickSource; + +/** Use with ecs_system_init() */ +typedef struct ecs_system_desc_t { + int32_t _canary; + + /** Existing entity to associate with system (optional) */ + ecs_entity_t entity; + + /** System query parameters */ + ecs_query_desc_t query; + + /** Callback that is invoked when a system is ran. + * When left to NULL, the default system runner is used, which calls the + * "callback" action for each result returned from the system's query. + * + * It should not be assumed that the input iterator can always be iterated + * with ecs_query_next(). When a system is multithreaded and/or paged, the + * iterator can be either a worker or paged iterator. Future use cases may + * introduce additional inputs for a system, such as rules and filters. The + * correct function to use for iteration is ecs_iter_next(). + * + * An implementation can test whether the iterator is a query iterator by + * testing whether the it->next value is equal to ecs_query_next(). */ + ecs_run_action_t run; + + /** Callback that is ran for each result returned by the system's query. This + * means that this callback can be invoked multiple times per system per + * frame, typically once for each matching table. */ + ecs_iter_action_t callback; + + /** Context to be passed to callback (as ecs_iter_t::param) */ + void *ctx; + + /** Binding context, for when system is implemented in other language */ + void *binding_ctx; + + /** Functions that are invoked during system cleanup to free context data. + * When set, functions are called unconditionally, even when the ctx + * pointers are NULL. */ + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t binding_ctx_free; + + /** Interval in seconds at which the system should run */ + ecs_ftime_t interval; + + /** Rate at which the system should run */ + int32_t rate; + + /** External tick source that determines when system ticks */ + ecs_entity_t tick_source; + + /** If true, system will be ran on multiple threads */ + bool multi_threaded; + + /** If true, system will have access to the actual world. Cannot be true at the + * same time as multi_threaded. */ + bool no_readonly; +} ecs_system_desc_t; + +/** Create a system */ +FLECS_API +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc); + +#ifndef FLECS_LEGACY + +/** Forward declare a system. */ +#define ECS_SYSTEM_DECLARE(id) ecs_entity_t ecs_id(id) + +/** Define a forward declared system. + * + * Example: + * + * @code + * ECS_SYSTEM_DEFINE(world, Move, EcsOnUpdate, Position, Velocity); + * @endcode + */ +#define ECS_SYSTEM_DEFINE(world, id_, phase, ...) \ + { \ + ecs_system_desc_t desc = {0}; \ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = ecs_id(id_);\ + edesc.name = #id_;\ + edesc.add[0] = ((phase) ? ecs_pair(EcsDependsOn, (phase)) : 0); \ + edesc.add[1] = (phase); \ + desc.entity = ecs_entity_init(world, &edesc);\ + desc.query.filter.expr = #__VA_ARGS__; \ + desc.callback = id_; \ + ecs_id(id_) = ecs_system_init(world, &desc); \ + } \ + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL) + +/** Declare & define a system. + * + * Example: + * + * @code + * ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + * @endcode + */ +#define ECS_SYSTEM(world, id, phase, ...) \ + ecs_entity_t ecs_id(id) = 0; ECS_SYSTEM_DEFINE(world, id, phase, __VA_ARGS__);\ + ecs_entity_t id = ecs_id(id);\ + (void)ecs_id(id);\ + (void)id + +/** Shorthand for creating a system with ecs_system_init(). + * + * Example: + * + * @code + * ecs_system(world, { + * .entity = ecs_entity(world, { + * .name = "MyEntity", + * .add = { ecs_dependson(EcsOnUpdate) } + * }), + * .query.filter.terms = { + * { ecs_id(Position) }, + * { ecs_id(Velocity) } + * }, + * .callback = Move + * }); + * @endcode + */ +#define ecs_system(world, ...)\ + ecs_system_init(world, &(ecs_system_desc_t) __VA_ARGS__ ) + +#endif + +/** 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 pre-matched 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 + * combination 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, + ecs_ftime_t delta_time, + void *param); + +/** Same as ecs_run(), but subdivides entities across number of provided stages. + * + * @param world The world. + * @param system The system to run. + * @param stage_current The id of the current stage. + * @param stage_count The total number of stages. + * @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_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t 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 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, + ecs_ftime_t delta_time, + int32_t offset, + int32_t limit, + void *param); + +/** Get the query object for a system. + * Systems use queries under the hood. This enables an application to get access + * to the underlying query object of a system. This can be useful when, for + * example, an application needs to enable sorting for a system. + * + * @param world The world. + * @param system The system from which to obtain the query. + * @return The query. + */ +FLECS_API +ecs_query_t* ecs_system_get_query( + const ecs_world_t *world, + ecs_entity_t system); + +/** Get system context. + * This operation returns the context pointer set for the system. If + * the provided entity is not a system, the function will return NULL. + * + * @param world The world. + * @param system The system from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_system_get_ctx( + const ecs_world_t *world, + ecs_entity_t system); + +/** Get system binding context. + * The binding context is a context typically used to attach any language + * binding specific data that is needed when invoking a callback that is + * implemented in another language. + * + * @param world The world. + * @param system The system from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_system_get_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system); + +FLECS_API +void FlecsSystemImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_STATS +#ifdef FLECS_NO_STATS +#error "FLECS_NO_STATS failed: STATS is required by other addons" +#endif +/** + * @file addons/stats.h + * @brief Statistics addon. + * + * The statistics addon enables an application to obtain detailed metrics about + * the storage, systems and operations of a world. + */ + +#ifdef FLECS_STATS + +/** + * @defgroup c_addons_stats Stats + * @ingroup c_addons + * Collection of statistics for world, queries, systems and pipelines. + * + * @{ + */ + +#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 { + ecs_float_t avg[ECS_STAT_WINDOW]; + ecs_float_t min[ECS_STAT_WINDOW]; + ecs_float_t max[ECS_STAT_WINDOW]; +} ecs_gauge_t; + +/** Monotonically increasing counter */ +typedef struct ecs_counter_t { + ecs_gauge_t rate; /**< Keep track of deltas too */ + double value[ECS_STAT_WINDOW]; +} ecs_counter_t; + +/** Make all metrics the same size, so we can iterate over fields */ +typedef union ecs_metric_t { + ecs_gauge_t gauge; + ecs_counter_t counter; +} ecs_metric_t; + +typedef struct ecs_world_stats_t { + int64_t first_; + + /* Entities */ + struct { + ecs_metric_t count; /**< Number of entities */ + ecs_metric_t not_alive_count; /**< Number of not alive (recyclable) entity ids */ + } entities; + + /* Component ids */ + struct { + ecs_metric_t tag_count; /**< Number of tag ids (ids without data) */ + ecs_metric_t component_count; /**< Number of components ids (ids with data) */ + ecs_metric_t pair_count; /**< Number of pair ids */ + ecs_metric_t type_count; /**< Number of registered types */ + ecs_metric_t create_count; /**< Number of times id has been created */ + ecs_metric_t delete_count; /**< Number of times id has been deleted */ + } components; + + /* Tables */ + struct { + ecs_metric_t count; /**< Number of tables */ + ecs_metric_t empty_count; /**< Number of empty tables */ + ecs_metric_t create_count; /**< Number of times table has been created */ + ecs_metric_t delete_count; /**< Number of times table has been deleted */ + } tables; + + /* Queries & events */ + struct { + ecs_metric_t query_count; /**< Number of queries */ + ecs_metric_t observer_count; /**< Number of observers */ + ecs_metric_t system_count; /**< Number of systems */ + } queries; + + /* Commands */ + struct { + ecs_metric_t add_count; + ecs_metric_t remove_count; + ecs_metric_t delete_count; + ecs_metric_t clear_count; + ecs_metric_t set_count; + ecs_metric_t get_mut_count; + ecs_metric_t modified_count; + ecs_metric_t other_count; + ecs_metric_t discard_count; + ecs_metric_t batched_entity_count; + ecs_metric_t batched_count; + } commands; + + /* Frame data */ + struct { + ecs_metric_t frame_count; /**< Number of frames processed. */ + ecs_metric_t merge_count; /**< Number of merges executed. */ + ecs_metric_t rematch_count; /**< Number of query rematches */ + ecs_metric_t pipeline_build_count; /**< Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_metric_t systems_ran; /**< Number of systems ran. */ + ecs_metric_t observers_ran; /**< Number of times an observer was invoked. */ + ecs_metric_t event_emit_count; /**< Number of events emitted */ + } frame; + + /* Timing */ + struct { + ecs_metric_t world_time_raw; /**< Actual time passed since simulation start (first time progress() is called) */ + ecs_metric_t world_time; /**< Simulation time passed since simulation start. Takes into account time scaling */ + ecs_metric_t frame_time; /**< Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_metric_t system_time; /**< Time spent on running systems. */ + ecs_metric_t emit_time; /**< Time spent on notifying observers. */ + ecs_metric_t merge_time; /**< Time spent on merging commands. */ + ecs_metric_t rematch_time; /**< Time spent on rematching. */ + ecs_metric_t fps; /**< Frames per second. */ + ecs_metric_t delta_time; /**< Delta_time. */ + } performance; + + struct { + /* Memory allocation data */ + ecs_metric_t alloc_count; /**< Allocs per frame */ + ecs_metric_t realloc_count; /**< Reallocs per frame */ + ecs_metric_t free_count; /**< Frees per frame */ + ecs_metric_t outstanding_alloc_count; /**< Difference between allocs & frees */ + + /* Memory allocator data */ + ecs_metric_t block_alloc_count; /**< Block allocations per frame */ + ecs_metric_t block_free_count; /**< Block frees per frame */ + ecs_metric_t block_outstanding_alloc_count; /**< Difference between allocs & frees */ + ecs_metric_t stack_alloc_count; /**< Page allocations per frame */ + ecs_metric_t stack_free_count; /**< Page frees per frame */ + ecs_metric_t stack_outstanding_alloc_count; /**< Difference between allocs & frees */ + } memory; + + /* HTTP statistics */ + struct { + ecs_metric_t request_received_count; + ecs_metric_t request_invalid_count; + ecs_metric_t request_handled_ok_count; + ecs_metric_t request_handled_error_count; + ecs_metric_t request_not_handled_count; + ecs_metric_t request_preflight_count; + ecs_metric_t send_ok_count; + ecs_metric_t send_error_count; + ecs_metric_t busy_count; + } http; + + int64_t last_; + + /** Current position in ring buffer */ + int32_t t; +} ecs_world_stats_t; + +/** Statistics for a single query (use ecs_query_stats_get()) */ +typedef struct ecs_query_stats_t { + int64_t first_; + ecs_metric_t matched_table_count; /**< Matched non-empty tables */ + ecs_metric_t matched_empty_table_count; /**< Matched empty tables */ + ecs_metric_t matched_entity_count; /**< Number of matched entities */ + int64_t last_; + + /** Current position in ring buffer */ + int32_t t; +} ecs_query_stats_t; + +/** Statistics for a single system (use ecs_system_stats_get()) */ +typedef struct ecs_system_stats_t { + int64_t first_; + ecs_metric_t time_spent; /**< Time spent processing a system */ + ecs_metric_t invoke_count; /**< Number of times system is invoked */ + int64_t last_; + + bool task; /**< Is system a task */ + + ecs_query_stats_t query; +} ecs_system_stats_t; + +/** Statistics for sync point */ +typedef struct ecs_sync_stats_t { + int64_t first_; + ecs_metric_t time_spent; + ecs_metric_t commands_enqueued; + int64_t last_; + + int32_t system_count; + bool multi_threaded; + bool no_readonly; +} ecs_sync_stats_t; + +/** Statistics for all systems in a pipeline. */ +typedef struct ecs_pipeline_stats_t { + /* Allow for initializing struct with {0} */ + int8_t canary_; + + /** 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_vec_t systems; + + /** Vector with sync point stats */ + ecs_vec_t sync_points; + + /** 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; + + /** Current position in ring buffer */ + int32_t t; + + int32_t system_count; /**< Number of systems in pipeline */ + int32_t active_system_count; /**< Number of active systems in pipeline */ + int32_t rebuild_count; /**< Number of times pipeline has rebuilt */ +} ecs_pipeline_stats_t; + +/** Get world statistics. + * + * @param world The world. + * @param stats Out parameter for statistics. + */ +FLECS_API +void ecs_world_stats_get( + const ecs_world_t *world, + ecs_world_stats_t *stats); + +/** Reduce source measurement window into single destination measurement. */ +FLECS_API +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src); + +/** Reduce last measurement into previous measurement, restore old value. */ +FLECS_API +void ecs_world_stats_reduce_last( + ecs_world_stats_t *stats, + const ecs_world_stats_t *old, + int32_t count); + +/** Repeat last measurement. */ +FLECS_API +void ecs_world_stats_repeat_last( + ecs_world_stats_t *stats); + +/** Copy last measurement from source to destination. */ +FLECS_API +void ecs_world_stats_copy_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src); + +FLECS_API +void ecs_world_stats_log( + const 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_query_stats_get( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *stats); + +/** Reduce source measurement window into single destination measurement. */ +FLECS_API +void ecs_query_stats_reduce( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src); + +/** Reduce last measurement into previous measurement, restore old value. */ +FLECS_API +void ecs_query_stats_reduce_last( + ecs_query_stats_t *stats, + const ecs_query_stats_t *old, + int32_t count); + +/** Repeat last measurement. */ +FLECS_API +void ecs_query_stats_repeat_last( + ecs_query_stats_t *stats); + +/** Copy last measurement from source to destination. */ +FLECS_API +void ecs_query_stats_copy_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src); + +#ifdef FLECS_SYSTEM +/** 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_system_stats_get( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *stats); + +/** Reduce source measurement window into single destination measurement */ +FLECS_API +void ecs_system_stats_reduce( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src); + +/** Reduce last measurement into previous measurement, restore old value. */ +FLECS_API +void ecs_system_stats_reduce_last( + ecs_system_stats_t *stats, + const ecs_system_stats_t *old, + int32_t count); + +/** Repeat last measurement. */ +FLECS_API +void ecs_system_stats_repeat_last( + ecs_system_stats_t *stats); + +/** Copy last measurement from source to destination. */ +FLECS_API +void ecs_system_stats_copy_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src); +#endif + +#ifdef FLECS_PIPELINE +/** 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_pipeline_stats_get( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *stats); + +/** Free pipeline stats. + * + * @param stats The stats to free. + */ +FLECS_API +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats); + +/** Reduce source measurement window into single destination measurement */ +FLECS_API +void ecs_pipeline_stats_reduce( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src); + +/** Reduce last measurement into previous measurement, restore old value. */ +FLECS_API +void ecs_pipeline_stats_reduce_last( + ecs_pipeline_stats_t *stats, + const ecs_pipeline_stats_t *old, + int32_t count); + +/** Repeat last measurement. */ +FLECS_API +void ecs_pipeline_stats_repeat_last( + ecs_pipeline_stats_t *stats); + +/** Copy last measurement to destination. + * This operation copies the last measurement into the destination. It does not + * modify the cursor. + * + * @param dst The metrics. + * @param src The metrics to copy. + */ +FLECS_API +void ecs_pipeline_stats_copy_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src); + +#endif + +/** Reduce all measurements from a window into a single measurement. */ +FLECS_API +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, + int32_t t_dst, + int32_t t_src); + +/** Reduce last measurement into previous measurement */ +FLECS_API +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t t, + int32_t count); + +/** Copy measurement */ +FLECS_API +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_METRICS +#ifdef FLECS_NO_METRICS +#error "FLECS_NO_METRICS failed: METRICS is required by other addons" +#endif +/** + * @file addons/metrics.h + * @brief Metrics module. + * + * The metrics module extracts metrics from components and makes them available + * through a unified component interface. + */ + +#ifdef FLECS_METRICS + +/** + * @defgroup c_addons_metrics Metrics + * @ingroup c_addons + * Collect user-defined metrics from ECS data. + * + * @{ + */ + +#ifndef FLECS_METRICS_H +#define FLECS_METRICS_H + +#ifndef FLECS_META +#define FLECS_META +#endif + +#ifndef FLECS_UNITS +#define FLECS_UNITS +#endif + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsMetrics); + +/** Tag added to metrics, and used as first element of metric kind pair */ +FLECS_API extern ECS_TAG_DECLARE(EcsMetric); + +/** Metric that has monotonically increasing value */ +FLECS_API extern ECS_TAG_DECLARE(EcsCounter); + +/** Counter metric that is auto-incremented by source value */ +FLECS_API extern ECS_TAG_DECLARE(EcsCounterIncrement); + +/** Counter metric that counts the number of entities with an id */ +FLECS_API extern ECS_TAG_DECLARE(EcsCounterId); + +/** Metric that represents current value */ +FLECS_API extern ECS_TAG_DECLARE(EcsGauge); + +/** Tag added to metric instances */ +FLECS_API extern ECS_TAG_DECLARE(EcsMetricInstance); + +/** Component with metric instance value */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsMetricValue); + +/** Component with entity source of metric instance */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsMetricSource); + +typedef struct EcsMetricValue { + double value; +} EcsMetricValue; + +typedef struct EcsMetricSource { + ecs_entity_t entity; +} EcsMetricSource; + +typedef struct ecs_metric_desc_t { + int32_t _canary; + + /** Entity associated with metric */ + ecs_entity_t entity; + + /** Entity associated with member that stores metric value. Must not be set + * at the same time as id. Cannot be combined with EcsCounterId. */ + ecs_entity_t member; + + /* Member dot expression. Can be used instead of member and supports nested + * members. Must be set together with id and should not be set at the same + * time as member. */ + const char *dotmember; + + /** Tracks whether entities have the specified component id. Must not be set + * at the same time as member. */ + ecs_id_t id; + + /** If id is a (R, *) wildcard and relationship R has the OneOf property, + * setting this value to true will track individual targets. + * If the kind is EcsCountId and the id is a (R, *) wildcard, this value + * will create a metric per target. */ + bool targets; + + /** Must be EcsGauge, EcsCounter, EcsCounterIncrement or EcsCounterId */ + ecs_entity_t kind; + + /** Description of metric. Will only be set if FLECS_DOC addon is enabled */ + const char *brief; +} ecs_metric_desc_t; + +/** Create a new metric. + * Metrics are entities that store values measured from a range of different + * properties in the ECS storage. Metrics provide a single unified interface to + * discovering and reading these values, which can be useful for monitoring + * utilities, or for debugging. + * + * Examples of properties that can be measured by metrics are: + * - Component member values + * - How long an entity has had a specific component + * - How long an entity has had a specific target for a relationship + * - How many entities have a specific component + * + * Metrics can either be created as a "gauge" or "counter". A gauge is a metric + * that represents the value of something at a specific point in time, for + * example "velocity". A counter metric represents a value that is monotonically + * increasing, for example "miles driven". + * + * There are three different kinds of counter metric kinds: + * - EcsCounter + * When combined with a member, this will store the actual value of the member + * in the metric. This is useful for values that are already counters, such as + * a MilesDriven component. + * This kind creates a metric per entity that has the member/id. + * + * - EcsCounterIncrement + * When combined with a member, this will increment the value of the metric by + * the value of the member * delta_time. This is useful for values that are + * not counters, such as a Velocity component. + * This kind creates a metric per entity that has the member. + * + * - EcsCounterId + * This metric kind will count the number of entities with a specific + * (component) id. This kind creates a single metric instance for regular ids, + * and a metric instance per target for wildcard ids when targets is set. + * + * @param world The world. + * @param desc Metric description. + * @return The metric entity. + */ +FLECS_API +ecs_entity_t ecs_metric_init( + ecs_world_t *world, + const ecs_metric_desc_t *desc); + +/** Shorthand for creating a metric with ecs_metric_init(). + * + * Example: + * + * @code + * ecs_metric(world, { + * .member = ecs_lookup_fullpath(world, "Position.x") + * .kind = EcsGauge + * }); + * @endcode + */ +#define ecs_metric(world, ...)\ + ecs_metric_init(world, &(ecs_metric_desc_t) __VA_ARGS__ ) + +/* Module import */ +FLECS_API +void FlecsMetricsImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_ALERTS +#ifdef FLECS_NO_ALERTS +#error "FLECS_NO_ALERTS failed: ALERTS is required by other addons" +#endif +/** + * @file addons/alerts.h + * @brief Alerts module. + * + * The alerts module enables applications to register alerts for when certain + * conditions are met. Alerts are registered as queries, and automatically + * become active when entities match the alert query. + */ + +#ifdef FLECS_ALERTS + +/** + * @defgroup c_addons_alerts Alerts + * @ingroup c_addons + * Create alerts from monitoring queries. + * + * @{ + */ + +#ifndef FLECS_ALERTS_H +#define FLECS_ALERTS_H + +#ifndef FLECS_RULES +#define FLECS_RULES +#endif + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_ALERT_MAX_SEVERITY_FILTERS (4) + +/* Module id */ +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsAlerts); + +/* Module components */ + +/** Tag added to alert, and used as first element of alert severity pair */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlert); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertInstance); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertsActive); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertTimeout); + +/* Alert severity tags */ +FLECS_API extern ECS_TAG_DECLARE(EcsAlertInfo); +FLECS_API extern ECS_TAG_DECLARE(EcsAlertWarning); +FLECS_API extern ECS_TAG_DECLARE(EcsAlertError); +FLECS_API extern ECS_TAG_DECLARE(EcsAlertCritical); + +/** Alert information. Added to each alert instance */ +typedef struct EcsAlertInstance { + char *message; +} EcsAlertInstance; + +/** Map with active alerts for entity. */ +typedef struct EcsAlertsActive { + int32_t info_count; + int32_t warning_count; + int32_t error_count; + ecs_map_t alerts; +} EcsAlertsActive; + +typedef struct ecs_alert_severity_filter_t { + ecs_entity_t severity; /* Severity kind */ + ecs_id_t with; /* Component to match */ + const char *var; /* Variable to match component on. Do not include the + * '$' character. Leave to NULL for $this. */ + int32_t _var_index; /* Index of variable in filter (do not set) */ +} ecs_alert_severity_filter_t; + +typedef struct ecs_alert_desc_t { + int32_t _canary; + + /** Entity associated with alert */ + ecs_entity_t entity; + + /** Alert query. An alert will be created for each entity that matches the + * specified query. The query must have at least one term that uses the + * $this variable (default). */ + ecs_filter_desc_t filter; + + /** Template for alert message. This string is used to generate the alert + * message and may refer to variables in the query result. The format for + * the template expressions is as specified by ecs_interpolate_string(). + * + * Examples: + * + * "$this has Position but not Velocity" + * "$this has a parent entity $parent without Position" + */ + const char *message; + + /** User friendly name. Will only be set if FLECS_DOC addon is enabled. */ + const char *doc_name; + + /** Description of alert. Will only be set if FLECS_DOC addon is enabled */ + const char *brief; + + /** Metric kind. Must be EcsAlertInfo, EcsAlertWarning, EcsAlertError or + * EcsAlertCritical. Defaults to EcsAlertError. */ + ecs_entity_t severity; + + /** Severity filters can be used to assign different severities to the same + * alert. This prevents having to create multiple alerts, and allows + * entities to transition between severities without resetting the + * alert duration (optional). */ + ecs_alert_severity_filter_t severity_filters[ECS_ALERT_MAX_SEVERITY_FILTERS]; + + /** The retain period specifies how long an alert must be inactive before it + * is cleared. This makes it easier to track noisy alerts. While an alert is + * inactive its duration won't increase. + * When the retain period is 0, the alert will clear immediately after it no + * longer matches the alert query. */ + ecs_ftime_t retain_period; + + /** Alert when member value is out of range. Uses the warning/error ranges + * assigned to the member in the MemberRanges component (optional). */ + ecs_entity_t member; + + /** (Component) id of member to monitor. If left to 0 this will be set to + * the parent entity of the member (optional). */ + ecs_id_t id; + + /** Variable from which to fetch the member (optional). When left to NULL + * 'id' will be obtained from $this. */ + const char *var; +} ecs_alert_desc_t; + +/** Create a new alert. + * An alert is a query that is evaluated periodically and creates alert + * instances for each entity that matches the query. Alerts can be used to + * automate detection of errors in an application. + * + * Alerts are automatically cleared when a query is no longer true for an alert + * instance. At most one alert instance will be created per matched entity. + * + * Alert instances have three components: + * - AlertInstance: contains the alert message for the instance + * - MetricSource: contains the entity that triggered the alert + * - MetricValue: contains how long the alert has been active + * + * Alerts reuse components from the metrics addon so that alert instances can be + * tracked and discovered as metrics. Just like metrics, alert instances are + * created as children of the alert. + * + * When an entity has active alerts, it will have the EcsAlertsActive component + * which contains a map with active alerts for the entity. This component + * will be automatically removed once all alerts are cleared for the entity. + * + * @param world The world. + * @param desc Alert description. + * @return The alert entity. + */ +FLECS_API +ecs_entity_t ecs_alert_init( + ecs_world_t *world, + const ecs_alert_desc_t *desc); + +#define ecs_alert(world, ...)\ + ecs_alert_init(world, &(ecs_alert_desc_t)__VA_ARGS__) + +/** Return number of active alerts for entity. + * When a valid alert entity is specified for the alert parameter, the operation + * will return whether the specified alert is active for the entity. When no + * alert is specified, the operation will return the total number of active + * alerts for the entity. + * + * @param world The world. + * @param entity The entity. + * @param alert The alert to test for (optional). + * @return The number of active alerts for the entity. + */ +FLECS_API +int32_t ecs_get_alert_count( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert); + +/** Return alert instance for specified alert. + * This operation returns the alert instance for the specified alert. If the + * alert is not active for the entity, the operation will return 0. + * + * @param world The world. + * @param entity The entity. + * @param alert The alert to test for. + * @return The alert instance for the specified alert. + */ +FLECS_API +ecs_entity_t ecs_get_alert( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert); + +/* Module import */ +FLECS_API +void FlecsAlertsImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_MONITOR +#ifdef FLECS_NO_MONITOR +#error "FLECS_NO_MONITOR failed: MONITOR is required by other addons" +#endif +/** + * @file addons/monitor.h + * @brief Doc module. + * + * The monitor module automatically tracks statistics from the stats addon and + * stores them in components. + */ + +#ifdef FLECS_MONITOR + +/** + * @defgroup c_addons_monitor Monitor + * @ingroup c_addons + * The monitor addon periodically tracks statistics for the world and systems. + * + * @{ + */ + +#ifndef FLECS_MONITOR_H +#define FLECS_MONITOR_H + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_STATS +#define FLECS_STATS +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsMonitor); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldStats); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldSummary); +FLECS_API extern ECS_COMPONENT_DECLARE(EcsPipelineStats); + +FLECS_API extern ecs_entity_t EcsPeriod1s; +FLECS_API extern ecs_entity_t EcsPeriod1m; +FLECS_API extern ecs_entity_t EcsPeriod1h; +FLECS_API extern ecs_entity_t EcsPeriod1d; +FLECS_API extern ecs_entity_t EcsPeriod1w; + +typedef struct { + ecs_ftime_t elapsed; + int32_t reduce_count; +} EcsStatsHeader; + +typedef struct { + EcsStatsHeader hdr; + ecs_world_stats_t stats; +} EcsWorldStats; + +typedef struct { + EcsStatsHeader hdr; + ecs_pipeline_stats_t stats; +} EcsPipelineStats; + +typedef struct { + /* Target FPS */ + double target_fps; /**< Target FPS */ + + /* Total time */ + double frame_time_total; /**< Total time spent processing a frame */ + double system_time_total; /**< Total time spent in systems */ + double merge_time_total; /**< Total time spent in merges */ + + /* Last frame time */ + double frame_time_last; /**< Time spent processing a frame */ + double system_time_last; /**< Time spent in systems */ + double merge_time_last; /**< Time spent in merges */ +} EcsWorldSummary; + +/* Module import */ +FLECS_API +void FlecsMonitorImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_COREDOC +#ifdef FLECS_NO_COREDOC +#error "FLECS_NO_COREDOC failed: COREDOC is required by other addons" +#endif +/** + * @file addons/coredoc.h + * @brief Core doc module. + * + * The core doc module imports documentation and reflection data for core + * components, tags and systems. + */ + +#ifdef FLECS_COREDOC + +#ifndef FLECS_DOC +#define FLECS_DOC +#endif + +#ifndef FLECS_META +#define FLECS_META +#endif + +#ifndef FLECS_COREDOC_H +#define FLECS_COREDOC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup c_addons_coredoc Coredoc + * @ingroup c_addons + * Module that adds documentation and reflection to core entities. + * + * @{ + */ + +/* Module import */ + +FLECS_API +void FlecsCoreDocImport( + ecs_world_t *world); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +#endif + +#ifdef FLECS_DOC +#ifdef FLECS_NO_DOC +#error "FLECS_NO_DOC failed: DOC is required by other addons" +#endif +/** + * @file addons/doc.h + * @brief Doc module. + * + * The doc module allows for documenting entities (and thus components, systems) + * by adding brief and/or detailed descriptions as components. Documentation + * added with the doc module can be retrieved at runtime, and can be used by + * tooling such as UIs or documentation frameworks. + */ + +#ifdef FLECS_DOC + +#ifndef FLECS_DOC_H +#define FLECS_DOC_H + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup c_addons_doc Doc + * @ingroup c_addons + * Utilities for documenting entities, components and systems. + * + * @{ + */ + +FLECS_API extern const ecs_entity_t ecs_id(EcsDocDescription); +FLECS_API extern const ecs_entity_t EcsDocBrief; +FLECS_API extern const ecs_entity_t EcsDocDetail; +FLECS_API extern const ecs_entity_t EcsDocLink; +FLECS_API extern const ecs_entity_t EcsDocColor; + +typedef struct EcsDocDescription { + char *value; +} EcsDocDescription; + +/** Add human-readable name to entity. + * Contrary to entity names, human readable names do not have to be unique and + * can contain special characters used in the query language like '*'. + * + * @param world The world. + * @param entity The entity to which to add the name. + * @param name The name to add. + * + * @see ecs_doc_get_name() + * @see flecs::doc::set_name() + * @see flecs::entity_builder::set_doc_name() + */ +FLECS_API +void ecs_doc_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +/** Add brief description to entity. + * + * @param world The world. + * @param entity The entity to which to add the description. + * @param description The description to add. + * + * @see ecs_doc_get_brief() + * @see flecs::doc::set_brief() + * @see flecs::entity_builder::set_doc_brief() + */ +FLECS_API +void ecs_doc_set_brief( + ecs_world_t *world, + ecs_entity_t entity, + const char *description); + +/** Add detailed description to entity. + * + * @param world The world. + * @param entity The entity to which to add the description. + * @param description The description to add. + * + * @see ecs_doc_get_detail() + * @see flecs::doc::set_detail() + * @see flecs::entity_builder::set_doc_detail() + */ +FLECS_API +void ecs_doc_set_detail( + ecs_world_t *world, + ecs_entity_t entity, + const char *description); + +/** Add link to external documentation to entity. + * + * @param world The world. + * @param entity The entity to which to add the link. + * @param link The link to add. + * + * @see ecs_doc_get_link() + * @see flecs::doc::set_link() + * @see flecs::entity_builder::set_doc_link() + */ +FLECS_API +void ecs_doc_set_link( + ecs_world_t *world, + ecs_entity_t entity, + const char *link); + +/** Add color to entity. + * UIs can use color as hint to improve visualizing entities. + * + * @param world The world. + * @param entity The entity to which to add the link. + * @param color The color to add. + * + * @see ecs_doc_get_color() + * @see flecs::doc::set_color() + * @see flecs::entity_builder::set_doc_color() + */ +FLECS_API +void ecs_doc_set_color( + ecs_world_t *world, + ecs_entity_t entity, + const char *color); + +/** Get human readable name from entity. + * If entity does not have an explicit human readable name, this operation will + * return the entity name. + * + * To test if an entity has a human readable name, use: + * + * @code + * ecs_has_pair(world, e, ecs_id(EcsDocDescription), EcsName); + * @endcode + * + * Or in C++: + * + * @code + * e.has(flecs::Name); + * @endcode + * + * @param world The world. + * @param entity The entity from which to get the name. + * @return The name. + * + * @see ecs_doc_set_name() + * @see flecs::doc::get_name() + * @see flecs::entity_view::get_doc_name() + */ +FLECS_API +const char* ecs_doc_get_name( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get brief description from entity. + * + * @param world The world. + * @param entity The entity from which to get the description. + * @return The description. + * + * @see ecs_doc_set_brief() + * @see flecs::doc::get_brief() + * @see flecs::entity_view::get_doc_brief() + */ +FLECS_API +const char* ecs_doc_get_brief( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get detailed description from entity. + * + * @param world The world. + * @param entity The entity from which to get the description. + * @return The description. + * + * @see ecs_doc_set_detail() + * @see flecs::doc::get_detail() + * @see flecs::entity_view::get_doc_detail() + */ +FLECS_API +const char* ecs_doc_get_detail( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get link to external documentation from entity. + * + * @param world The world. + * @param entity The entity from which to get the link. + * @return The link. + * + * @see ecs_doc_set_link() + * @see flecs::doc::get_link() + * @see flecs::entity_view::get_doc_link() + */ +FLECS_API +const char* ecs_doc_get_link( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get color from entity. + * + * @param world The world. + * @param entity The entity from which to get the color. + * @return The color. + * + * @see ecs_doc_set_color() + * @see flecs::doc::get_color() + * @see flecs::entity_view::get_doc_color() + */ +FLECS_API +const char* ecs_doc_get_color( + const ecs_world_t *world, + ecs_entity_t entity); + +/* Module import */ +FLECS_API +void FlecsDocImport( + ecs_world_t *world); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +#endif + +#ifdef FLECS_JSON +#ifdef FLECS_NO_JSON +#error "FLECS_NO_JSON failed: JSON is required by other addons" +#endif +/** + * @file addons/json.h + * @brief JSON parser addon. + * + * Parse expression strings into component values. Entity identifiers, + * enumerations and bitmasks are encoded as strings. + * + * See docs/JsonFormat.md for a description of the JSON format. + */ + +#ifdef FLECS_JSON + +#ifndef FLECS_EXPR +#define FLECS_EXPR +#endif + +#ifndef FLECS_JSON_H +#define FLECS_JSON_H + +/** + * @defgroup c_addons_json Json + * @ingroup c_addons + * Functions for serializing to/from JSON. + * + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Used with ecs_ptr_from_json(), ecs_entity_from_json(). */ +typedef struct ecs_from_json_desc_t { + const char *name; /**< Name of expression (used for logging) */ + const char *expr; /**< Full expression (used for logging) */ + + /** Callback that allows for specifying a custom lookup function. The + * default behavior uses ecs_lookup_fullpath() */ + ecs_entity_t (*lookup_action)( + const ecs_world_t*, + const char *value, + void *ctx); + void *lookup_ctx; +} ecs_from_json_desc_t; + +/** Parse JSON string into value. + * This operation parses a JSON expression into the provided pointer. The + * memory pointed to must be large enough to contain a value of the used type. + * + * @param world The world. + * @param type The type of the expression to parse. + * @param ptr Pointer to the memory to write to. + * @param json The JSON expression to parse. + * @param desc Configuration parameters for deserializer. + * @return Pointer to the character after the last one read, or NULL if failed. + */ +FLECS_API +const char* ecs_ptr_from_json( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr, + const char *json, + const ecs_from_json_desc_t *desc); + +/** Parse JSON object with multiple component values into entity. The format + * is the same as the one outputted by ecs_entity_to_json(), but at the moment + * only supports the "ids" and "values" member. + * + * @param world The world. + * @param entity The entity to serialize to. + * @param json The JSON expression to parse (see entity in JSON format manual). + * @param desc Configuration parameters for deserializer. + * @return Pointer to the character after the last one read, or NULL if failed. + */ +FLECS_API +const char* ecs_entity_from_json( + ecs_world_t *world, + ecs_entity_t entity, + const char *json, + const ecs_from_json_desc_t *desc); + +/** Parse JSON object with multiple entities into the world. The format is the + * same as the one outputted by ecs_world_to_json(). + * + * @param world The world. + * @param json The JSON expression to parse (see iterator in JSON format manual). + */ +FLECS_API +const char* ecs_world_from_json( + ecs_world_t *world, + const char *json, + const ecs_from_json_desc_t *desc); + +/** Serialize array into JSON string. + * This operation serializes a value of the provided type to a JSON string. The + * memory pointed to must be large enough to contain a value of the used type. + * + * If count is 0, the function will serialize a single value, not wrapped in + * array brackets. If count is >= 1, the operation will serialize values to a + * a comma-separated list inside of array brackets. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param count The number of elements to serialize. + * @return String with JSON expression, or NULL if failed. + */ +FLECS_API +char* ecs_array_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + int32_t count); + +/** Serialize array into JSON string buffer. + * Same as ecs_array_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param count The number of elements to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_array_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + int32_t count, + ecs_strbuf_t *buf_out); + +/** Serialize value into JSON string. + * Same as ecs_array_to_json(), with count = 0. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @return String with JSON expression, or NULL if failed. + */ +FLECS_API +char* ecs_ptr_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void *data); + +/** Serialize value into JSON string buffer. + * Same as ecs_ptr_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_ptr_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + ecs_strbuf_t *buf_out); + +/** Serialize type info to JSON. + * This serializes type information to JSON, and can be used to store/transmit + * the structure of a (component) value. + * + * If the provided type does not have reflection data, "0" will be returned. + * + * @param world The world. + * @param type The type to serialize to JSON. + * @return A JSON string with the serialized type info, or NULL if failed. + */ +FLECS_API +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type); + +/** Serialize type info into JSON string buffer. + * Same as ecs_type_info_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf_out); + +/** Used with ecs_iter_to_json(). */ +typedef struct ecs_entity_to_json_desc_t { + bool serialize_path; /**< Serialize full pathname */ + bool serialize_label; /**< Serialize doc name */ + bool serialize_brief; /**< Serialize brief doc description */ + bool serialize_link; /**< Serialize doc link (URL) */ + bool serialize_color; /**< Serialize doc color */ + bool serialize_ids; /**< Serialize (component) ids */ + bool serialize_id_labels; /**< Serialize labels of (component) ids */ + bool serialize_base; /**< Serialize base components */ + bool serialize_private; /**< Serialize private components */ + bool serialize_hidden; /**< Serialize ids hidden by override */ + bool serialize_values; /**< Serialize component values */ + bool serialize_type_info; /**< Serialize type info (requires serialize_values) */ + bool serialize_alerts; /**< Serialize active alerts for entity */ + ecs_entity_t serialize_refs; /**< Serialize references (incoming edges) for relationship */ + bool serialize_matches; /**< Serialize which queries entity matches with */ +} ecs_entity_to_json_desc_t; + +#define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t){true, false,\ + false, false, false, true, false, true, false, false, false, false, false,\ + false, false } + +/** Serialize entity into JSON string. + * This creates a JSON object with the entity's (path) name, which components + * and tags the entity has, and the component values. + * + * The operation may fail if the entity contains components with invalid values. + * + * @param world The world. + * @param entity The entity to serialize to JSON. + * @return A JSON string with the serialized entity data, or NULL if failed. + */ +FLECS_API +char* ecs_entity_to_json( + const ecs_world_t *world, + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc); + +/** Serialize entity into JSON string buffer. + * Same as ecs_entity_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param entity The entity to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_entity_to_json_buf( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_strbuf_t *buf_out, + const ecs_entity_to_json_desc_t *desc); + +/** Used with ecs_iter_to_json(). */ +typedef struct ecs_iter_to_json_desc_t { + bool serialize_term_ids; /**< Serialize query term component ids */ + bool serialize_term_labels; /**< Serialize query term component id labels */ + bool serialize_ids; /**< Serialize actual (matched) component ids */ + bool serialize_id_labels; /**< Serialize actual (matched) component id labels */ + bool serialize_sources; /**< Serialize sources */ + bool serialize_variables; /**< Serialize variables */ + bool serialize_is_set; /**< Serialize is_set (for optional terms) */ + bool serialize_values; /**< Serialize component values */ + bool serialize_private; /**< Serialize component values */ + bool serialize_entities; /**< Serialize entities (for This terms) */ + bool serialize_entity_labels; /**< Serialize doc name for entities */ + bool serialize_entity_ids; /**< Serialize numerical ids for entities */ + bool serialize_entity_names; /**< Serialize names (not paths) for entities */ + bool serialize_variable_labels; /**< Serialize doc name for variables */ + bool serialize_variable_ids; /**< Serialize numerical ids for variables */ + bool serialize_colors; /**< Serialize doc color for entities */ + bool measure_eval_duration; /**< Serialize evaluation duration */ + bool serialize_type_info; /**< Serialize type information */ + bool serialize_table; /**< Serialize entire table vs. matched components */ + bool serialize_rows; /**< Use row-based serialization, with entities in separate elements */ + bool serialize_field_info; /**< Serialize metadata for fields returned by query */ + bool dont_serialize_results; /**< If true, query won't be evaluated */ +} ecs_iter_to_json_desc_t; + +#define ECS_ITER_TO_JSON_INIT (ecs_iter_to_json_desc_t){\ + .serialize_term_ids = true, \ + .serialize_term_labels = false, \ + .serialize_ids = true, \ + .serialize_id_labels = false, \ + .serialize_sources = true, \ + .serialize_variables = true, \ + .serialize_is_set = true, \ + .serialize_values = true, \ + .serialize_entities = true, \ + .serialize_entity_labels = false, \ + .serialize_entity_ids = false, \ + .serialize_entity_names = false, \ + .serialize_variable_labels = false, \ + .serialize_variable_ids = false, \ + .serialize_colors = false, \ + .measure_eval_duration = false, \ + .serialize_type_info = false, \ + .serialize_table = false, \ + .serialize_rows = false, \ + .serialize_field_info = false, \ + .dont_serialize_results = false, \ +} + +/** Serialize iterator into JSON string. + * This operation will iterate the contents of the iterator and serialize them + * to JSON. The function accepts iterators from any source. + * + * @param world The world. + * @param iter The iterator to serialize to JSON. + * @return A JSON string with the serialized iterator data, or NULL if failed. + */ +FLECS_API +char* ecs_iter_to_json( + const ecs_world_t *world, + ecs_iter_t *iter, + const ecs_iter_to_json_desc_t *desc); + +/** Serialize iterator into JSON string buffer. + * Same as ecs_iter_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param iter The iterator to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_iter_to_json_buf( + const ecs_world_t *world, + ecs_iter_t *iter, + ecs_strbuf_t *buf_out, + const ecs_iter_to_json_desc_t *desc); + +/** Used with ecs_iter_to_json(). */ +typedef struct ecs_world_to_json_desc_t { + bool serialize_builtin; /**< Exclude flecs modules & contents */ + bool serialize_modules; /**< Exclude modules & contents */ +} ecs_world_to_json_desc_t; + +/** Serialize world into JSON string. + * This operation iterates the contents of the world to JSON. The operation is + * equivalent to the following code: + * + * @code + * ecs_filter_t *f = ecs_filter(world, { + * .terms = {{ .id = EcsAny }} + * }); + * + * ecs_iter_t it = ecs_filter_init(world, &f); + * ecs_iter_to_json_desc_t desc = { .serialize_table = true }; + * ecs_iter_to_json(world, iter, &desc); + * @endcode + * + * @param world The world to serialize. + * @return A JSON string with the serialized iterator data, or NULL if failed. + */ +FLECS_API +char* ecs_world_to_json( + ecs_world_t *world, + const ecs_world_to_json_desc_t *desc); + +/** Serialize world into JSON string buffer. + * Same as ecs_world_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_world_to_json_buf( + ecs_world_t *world, + ecs_strbuf_t *buf_out, + const ecs_world_to_json_desc_t *desc); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#if defined(FLECS_EXPR) || defined(FLECS_META_C) +#ifndef FLECS_META +#define FLECS_META +#endif +#endif +#ifdef FLECS_UNITS +#ifdef FLECS_NO_UNITS +#error "FLECS_NO_UNITS failed: UNITS is required by other addons" +#endif +/** + * @file addons/units.h + * @brief Units module. + * + * Builtin standard units. The units addon is not imported by default, even if + * the addon is included in the build. To import the module, do: + * + * In C: + * + * @code + * ECS_IMPORT(world, FlecsUnits); + * @endcode + * + * In C++: + * + * @code + * world.import(); + * @endcode + * + * As a result this module behaves just like an application-defined module, + * which means that the ids generated for the entities inside the module are not + * fixed, and depend on the order in which the module is imported. + */ + +#ifdef FLECS_UNITS + +/** + * @defgroup c_addons_units Units. + * @ingroup c_addons + * Common unit annotations for reflection framework. + * + * @{ + */ + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_META +#define FLECS_META +#endif + +#ifndef FLECS_UNITS_H +#define FLECS_UNITS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup c_addons_units_prefixes Prefixes + * @ingroup c_addons_units + * Prefixes to indicate unit count (e.g. Kilo, Mega) + * + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsUnitPrefixes); /* Parent scope for prefixes */ + +FLECS_API extern ECS_DECLARE(EcsYocto); +FLECS_API extern ECS_DECLARE(EcsZepto); +FLECS_API extern ECS_DECLARE(EcsAtto); +FLECS_API extern ECS_DECLARE(EcsFemto); +FLECS_API extern ECS_DECLARE(EcsPico); +FLECS_API extern ECS_DECLARE(EcsNano); +FLECS_API extern ECS_DECLARE(EcsMicro); +FLECS_API extern ECS_DECLARE(EcsMilli); +FLECS_API extern ECS_DECLARE(EcsCenti); +FLECS_API extern ECS_DECLARE(EcsDeci); +FLECS_API extern ECS_DECLARE(EcsDeca); +FLECS_API extern ECS_DECLARE(EcsHecto); +FLECS_API extern ECS_DECLARE(EcsKilo); +FLECS_API extern ECS_DECLARE(EcsMega); +FLECS_API extern ECS_DECLARE(EcsGiga); +FLECS_API extern ECS_DECLARE(EcsTera); +FLECS_API extern ECS_DECLARE(EcsPeta); +FLECS_API extern ECS_DECLARE(EcsExa); +FLECS_API extern ECS_DECLARE(EcsZetta); +FLECS_API extern ECS_DECLARE(EcsYotta); + +FLECS_API extern ECS_DECLARE(EcsKibi); +FLECS_API extern ECS_DECLARE(EcsMebi); +FLECS_API extern ECS_DECLARE(EcsGibi); +FLECS_API extern ECS_DECLARE(EcsTebi); +FLECS_API extern ECS_DECLARE(EcsPebi); +FLECS_API extern ECS_DECLARE(EcsExbi); +FLECS_API extern ECS_DECLARE(EcsZebi); +FLECS_API extern ECS_DECLARE(EcsYobi); + +/** @} */ + +/** + * @defgroup c_addons_units_duration Duration + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsDuration); +FLECS_API extern ECS_DECLARE(EcsPicoSeconds); +FLECS_API extern ECS_DECLARE(EcsNanoSeconds); +FLECS_API extern ECS_DECLARE(EcsMicroSeconds); +FLECS_API extern ECS_DECLARE(EcsMilliSeconds); +FLECS_API extern ECS_DECLARE(EcsSeconds); +FLECS_API extern ECS_DECLARE(EcsMinutes); +FLECS_API extern ECS_DECLARE(EcsHours); +FLECS_API extern ECS_DECLARE(EcsDays); + +/** @} */ + +/** + * @defgroup c_addons_units_time Time + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsTime); +FLECS_API extern ECS_DECLARE(EcsDate); + +/** @} */ + +/** + * @defgroup c_addons_units_mass Mass + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsMass); +FLECS_API extern ECS_DECLARE(EcsGrams); +FLECS_API extern ECS_DECLARE(EcsKiloGrams); + +/** @} */ + +/** + * @defgroup c_addons_units_electric_Current Electric Current + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsElectricCurrent); +FLECS_API extern ECS_DECLARE(EcsAmpere); + +/** @} */ + +/** + * @defgroup c_addons_units_amount Amount + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsAmount); +FLECS_API extern ECS_DECLARE(EcsMole); + +/** @} */ + +/** + * @defgroup c_addons_units_luminous_intensity Luminous Intensity + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsLuminousIntensity); +FLECS_API extern ECS_DECLARE(EcsCandela); + +/** @} */ + +/** + * @defgroup c_addons_units_force Force + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsForce); +FLECS_API extern ECS_DECLARE(EcsNewton); + +/** @} */ + +/** + * @defgroup c_addons_units_length Length + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsLength); +FLECS_API extern ECS_DECLARE(EcsMeters); +FLECS_API extern ECS_DECLARE(EcsPicoMeters); +FLECS_API extern ECS_DECLARE(EcsNanoMeters); +FLECS_API extern ECS_DECLARE(EcsMicroMeters); +FLECS_API extern ECS_DECLARE(EcsMilliMeters); +FLECS_API extern ECS_DECLARE(EcsCentiMeters); +FLECS_API extern ECS_DECLARE(EcsKiloMeters); +FLECS_API extern ECS_DECLARE(EcsMiles); +FLECS_API extern ECS_DECLARE(EcsPixels); + +/** @} */ + +/** + * @defgroup c_addons_units_pressure Pressure + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsPressure); +FLECS_API extern ECS_DECLARE(EcsPascal); +FLECS_API extern ECS_DECLARE(EcsBar); + +/** @} */ + +/** + * @defgroup c_addons_units_speed Speed + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsSpeed); +FLECS_API extern ECS_DECLARE(EcsMetersPerSecond); +FLECS_API extern ECS_DECLARE(EcsKiloMetersPerSecond); +FLECS_API extern ECS_DECLARE(EcsKiloMetersPerHour); +FLECS_API extern ECS_DECLARE(EcsMilesPerHour); + +/** @} */ + +/** + * @defgroup c_addons_units_temperature Temperature + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsTemperature); +FLECS_API extern ECS_DECLARE(EcsKelvin); +FLECS_API extern ECS_DECLARE(EcsCelsius); +FLECS_API extern ECS_DECLARE(EcsFahrenheit); + +/** @} */ + +/** + * @defgroup c_addons_units_data Data + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsData); +FLECS_API extern ECS_DECLARE(EcsBits); +FLECS_API extern ECS_DECLARE(EcsKiloBits); +FLECS_API extern ECS_DECLARE(EcsMegaBits); +FLECS_API extern ECS_DECLARE(EcsGigaBits); +FLECS_API extern ECS_DECLARE(EcsBytes); +FLECS_API extern ECS_DECLARE(EcsKiloBytes); +FLECS_API extern ECS_DECLARE(EcsMegaBytes); +FLECS_API extern ECS_DECLARE(EcsGigaBytes); +FLECS_API extern ECS_DECLARE(EcsKibiBytes); +FLECS_API extern ECS_DECLARE(EcsMebiBytes); +FLECS_API extern ECS_DECLARE(EcsGibiBytes); + +/** @} */ + +/** + * @defgroup c_addons_units_datarate Data Rate + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsDataRate); +FLECS_API extern ECS_DECLARE(EcsBitsPerSecond); +FLECS_API extern ECS_DECLARE(EcsKiloBitsPerSecond); +FLECS_API extern ECS_DECLARE(EcsMegaBitsPerSecond); +FLECS_API extern ECS_DECLARE(EcsGigaBitsPerSecond); +FLECS_API extern ECS_DECLARE(EcsBytesPerSecond); +FLECS_API extern ECS_DECLARE(EcsKiloBytesPerSecond); +FLECS_API extern ECS_DECLARE(EcsMegaBytesPerSecond); +FLECS_API extern ECS_DECLARE(EcsGigaBytesPerSecond); + +/** @} */ + +/** + * @defgroup c_addons_units_duration Duration + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsAngle); +FLECS_API extern ECS_DECLARE(EcsRadians); +FLECS_API extern ECS_DECLARE(EcsDegrees); + +/** @} */ + +/** + * @defgroup c_addons_units_angle Angle + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsFrequency); +FLECS_API extern ECS_DECLARE(EcsHertz); +FLECS_API extern ECS_DECLARE(EcsKiloHertz); +FLECS_API extern ECS_DECLARE(EcsMegaHertz); +FLECS_API extern ECS_DECLARE(EcsGigaHertz); + +/** @} */ + +/** + * @defgroup c_addons_units_uri Uri + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsUri); +FLECS_API extern ECS_DECLARE(EcsUriHyperlink); +FLECS_API extern ECS_DECLARE(EcsUriImage); +FLECS_API extern ECS_DECLARE(EcsUriFile); + +/** @} */ + +FLECS_API extern ECS_DECLARE(EcsAcceleration); +FLECS_API extern ECS_DECLARE(EcsPercentage); +FLECS_API extern ECS_DECLARE(EcsBel); +FLECS_API extern ECS_DECLARE(EcsDeciBel); + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void FlecsUnitsImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_META +#ifdef FLECS_NO_META +#error "FLECS_NO_META failed: META is required by other addons" +#endif +/** + * @file addons/meta.h + * @brief Meta addon. + * + * The meta addon enables reflecting on component data. Types are stored as + * entities, with components that store the reflection data. A type has at least + * two components: + * + * - EcsComponent: core component, contains size & alignment + * - EcsMetaType: component that indicates what kind of type the entity is + * + * Additionally the type may have an additional component that contains the + * reflection data for the type. For example, structs have these components: + * + * - EcsComponent + * - EcsMetaType + * - EcsStruct + * + * Structs can be populated by adding child entities with the EcsMember + * component. Adding a child with a Member component to an entity will + * automatically add the EcsStruct component to the parent. + * + * Enums/bitmasks can be populated by adding child entities with the Constant + * tag. By default constants are automatically assigned values when they are + * added to the enum/bitmask. The parent entity must have the EcsEnum or + * EcsBitmask component before adding the constants. + * + * To create enum constants with a manual value, set (Constant, i32) to the + * desired value. To create bitmask constants with a manual value, set + * (Constant, u32) to the desired value. Constants with manual values should not + * conflict with other constants. + * + * The _init APIs are convenience wrappers around creating the entities and + * components for the types. + * + * When a type is created it automatically receives the EcsComponent and + * EcsMetaType components. The former means that the resulting type can be + * used as a regular component: + * + * @code + * // Create Position type + * ecs_entity_t pos = ecs_struct_init(world, &(ecs_struct_desc_t){ + * .entity.name = "Position", + * .members = { + * {"x", ecs_id(ecs_f32_t)}, + * {"y", ecs_id(ecs_f32_t)} + * } + * }); + * + * // Create entity with Position component + * ecs_entity_t e = ecs_new_w_id(world, pos); + * @endcode + * + * Type entities do not have to be named. + */ + +#ifdef FLECS_META + +/** + * @defgroup c_addons_meta Meta + * @ingroup c_addons + * Flecs reflection framework. + * + * @{ + */ + +#include + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_META_H +#define FLECS_META_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_MEMBER_DESC_CACHE_SIZE (32) + +/** Primitive type definitions. + * These typedefs allow the builtin primitives to be used as regular components: + * + * @code + * ecs_set(world, e, ecs_i32_t, {10}); + * @endcode + * + * Or a more useful example (create an enum constant with a manual value): + * + * @code + * ecs_set_pair_object(world, e, EcsConstant, ecs_i32_t, {10}); + * @endcode + */ +typedef bool ecs_bool_t; +typedef char ecs_char_t; +typedef unsigned char ecs_byte_t; +typedef uint8_t ecs_u8_t; +typedef uint16_t ecs_u16_t; +typedef uint32_t ecs_u32_t; +typedef uint64_t ecs_u64_t; +typedef uintptr_t ecs_uptr_t; +typedef int8_t ecs_i8_t; +typedef int16_t ecs_i16_t; +typedef int32_t ecs_i32_t; +typedef int64_t ecs_i64_t; +typedef intptr_t ecs_iptr_t; +typedef float ecs_f32_t; +typedef double ecs_f64_t; +typedef char* ecs_string_t; + +/* Meta module component ids */ +FLECS_API extern const ecs_entity_t ecs_id(EcsMetaType); +FLECS_API extern const ecs_entity_t ecs_id(EcsMetaTypeSerialized); +FLECS_API extern const ecs_entity_t ecs_id(EcsPrimitive); +FLECS_API extern const ecs_entity_t ecs_id(EcsEnum); +FLECS_API extern const ecs_entity_t ecs_id(EcsBitmask); +FLECS_API extern const ecs_entity_t ecs_id(EcsMember); +FLECS_API extern const ecs_entity_t ecs_id(EcsMemberRanges); +FLECS_API extern const ecs_entity_t ecs_id(EcsStruct); +FLECS_API extern const ecs_entity_t ecs_id(EcsArray); +FLECS_API extern const ecs_entity_t ecs_id(EcsVector); +FLECS_API extern const ecs_entity_t ecs_id(EcsOpaque); +FLECS_API extern const ecs_entity_t ecs_id(EcsUnit); +FLECS_API extern const ecs_entity_t ecs_id(EcsUnitPrefix); +FLECS_API extern const ecs_entity_t EcsConstant; +FLECS_API extern const ecs_entity_t EcsQuantity; + +/* Primitive type component ids */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_bool_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_char_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_byte_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_u8_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_u16_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_u32_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_u64_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_uptr_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_i8_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_i16_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_i32_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_i64_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_iptr_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_f32_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_f64_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_string_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_entity_t); +FLECS_API extern const ecs_entity_t ecs_id(ecs_id_t); + +/** Type kinds supported by meta addon */ +typedef enum ecs_type_kind_t { + EcsPrimitiveType, + EcsBitmaskType, + EcsEnumType, + EcsStructType, + EcsArrayType, + EcsVectorType, + EcsOpaqueType, + EcsTypeKindLast = EcsOpaqueType +} ecs_type_kind_t; + +/** Component that is automatically added to every type with the right kind. */ +typedef struct EcsMetaType { + ecs_type_kind_t kind; + bool existing; /**< Did the type exist or is it populated from reflection */ + bool partial; /**< Is the reflection data a partial type description */ +} EcsMetaType; + +/** Primitive type kinds supported by meta addon */ +typedef enum ecs_primitive_kind_t { + EcsBool = 1, + EcsChar, + EcsByte, + EcsU8, + EcsU16, + EcsU32, + EcsU64, + EcsI8, + EcsI16, + EcsI32, + EcsI64, + EcsF32, + EcsF64, + EcsUPtr, + EcsIPtr, + EcsString, + EcsEntity, + EcsId, + EcsPrimitiveKindLast = EcsId +} ecs_primitive_kind_t; + +/** Component added to primitive types */ +typedef struct EcsPrimitive { + ecs_primitive_kind_t kind; +} EcsPrimitive; + +/** Component added to member entities */ +typedef struct EcsMember { + ecs_entity_t type; + int32_t count; + ecs_entity_t unit; + int32_t offset; +} EcsMember; + +/** Type expressing a range for a member value */ +typedef struct ecs_member_value_range_t { + double min; + double max; +} ecs_member_value_range_t; + +/** Component added to member entities to express valid value ranges */ +typedef struct EcsMemberRanges { + ecs_member_value_range_t value; + ecs_member_value_range_t warning; + ecs_member_value_range_t error; +} EcsMemberRanges; + +/** Element type of members vector in EcsStruct */ +typedef struct ecs_member_t { + /** Must be set when used with ecs_struct_desc_t */ + const char *name; + ecs_entity_t type; + + /** May be set when used with ecs_struct_desc_t */ + int32_t count; + int32_t offset; + + /** May be set when used with ecs_struct_desc_t, will be auto-populated if + * type entity is also a unit */ + ecs_entity_t unit; + + /** Numerical range that specifies which values member can assume. This + * range may be used by UI elements such as a progress bar or slider. The + * value of a member should not exceed this range. */ + ecs_member_value_range_t range; + + /** Numerical range outside of which the value represents an error. This + * range may be used by UI elements to style a value. */ + ecs_member_value_range_t error_range; + + /** Numerical range outside of which the value represents an warning. This + * range may be used by UI elements to style a value. */ + ecs_member_value_range_t warning_range; + + /** Should not be set by ecs_struct_desc_t */ + ecs_size_t size; + ecs_entity_t member; +} ecs_member_t; + +/** Component added to struct type entities */ +typedef struct EcsStruct { + /** Populated from child entities with Member component */ + ecs_vec_t members; /* vector */ +} EcsStruct; + +typedef struct ecs_enum_constant_t { + /** Must be set when used with ecs_enum_desc_t */ + const char *name; + + /** May be set when used with ecs_enum_desc_t */ + int32_t value; + + /** Should not be set by ecs_enum_desc_t */ + ecs_entity_t constant; +} ecs_enum_constant_t; + +/** Component added to enum type entities */ +typedef struct EcsEnum { + /** Populated from child entities with Constant component */ + ecs_map_t constants; /* map */ +} EcsEnum; + +typedef struct ecs_bitmask_constant_t { + /** Must be set when used with ecs_bitmask_desc_t */ + const char *name; + + /** May be set when used with ecs_bitmask_desc_t */ + ecs_flags32_t value; + + /** Should not be set by ecs_bitmask_desc_t */ + ecs_entity_t constant; +} ecs_bitmask_constant_t; + +/** Component added to bitmask type entities */ +typedef struct EcsBitmask { + /* Populated from child entities with Constant component */ + ecs_map_t constants; /* map */ +} EcsBitmask; + +/** Component added to array type entities */ +typedef struct EcsArray { + ecs_entity_t type; /**< Element type */ + int32_t count; /**< Number of elements */ +} EcsArray; + +/** Component added to vector type entities */ +typedef struct EcsVector { + ecs_entity_t type; /**< Element type */ +} EcsVector; + + +/* Opaque type support */ + +#if !defined(__cplusplus) || !defined(FLECS_CPP) + +/** Serializer interface */ +typedef struct ecs_serializer_t { + /* Serialize value */ + int (*value)( + const struct ecs_serializer_t *ser, /**< Serializer */ + ecs_entity_t type, /**< Type of the value to serialize */ + const void *value); /**< Pointer to the value to serialize */ + + /* Serialize member */ + int (*member)( + const struct ecs_serializer_t *ser, /**< Serializer */ + const char *member); /**< Member name */ + + const ecs_world_t *world; + void *ctx; +} ecs_serializer_t; + +#elif defined(__cplusplus) + +} /* extern "C" { */ + +/** Serializer interface (same layout as C, but with convenience methods) */ +typedef struct ecs_serializer_t { + /* Serialize value */ + int (*value_)( + const struct ecs_serializer_t *ser, + ecs_entity_t type, + const void *value); + + /* Serialize member */ + int (*member_)( + const struct ecs_serializer_t *ser, + const char *name); + + /* Serialize value */ + int value(ecs_entity_t type, const void *value) const; + + /* Serialize value */ + template + int value(const T& value) const; + + /* Serialize member */ + int member(const char *name) const; + + const ecs_world_t *world; + void *ctx; +} ecs_serializer_t; + +extern "C" { +#endif + +/** Callback invoked serializing an opaque type. */ +typedef int (*ecs_meta_serialize_t)( + const ecs_serializer_t *ser, + const void *src); /**< Pointer to value to serialize */ + +typedef struct EcsOpaque { + ecs_entity_t as_type; /**< Type that describes the serialized output */ + ecs_meta_serialize_t serialize; /**< Serialize action */ + + /* Deserializer interface + * Only override the callbacks that are valid for the opaque type. If a + * deserializer attempts to assign a value type that is not supported by the + * interface, a conversion error is thrown. + */ + + /** Assign bool value */ + void (*assign_bool)( + void *dst, + bool value); + + /** Assign char value */ + void (*assign_char)( + void *dst, + char value); + + /** Assign int value */ + void (*assign_int)( + void *dst, + int64_t value); + + /** Assign unsigned int value */ + void (*assign_uint)( + void *dst, + uint64_t value); + + /** Assign float value */ + void (*assign_float)( + void *dst, + double value); + + /** Assign string value */ + void (*assign_string)( + void *dst, + const char *value); + + /** Assign entity value */ + void (*assign_entity)( + void *dst, + ecs_world_t *world, + ecs_entity_t entity); + + /** Assign (component) id value */ + void (*assign_id)( + void *dst, + ecs_world_t *world, + ecs_id_t id); + + /** Assign null value */ + void (*assign_null)( + void *dst); + + /** Clear collection elements */ + void (*clear)( + void *dst); + + /** Ensure & get collection element */ + void* (*ensure_element)( + void *dst, + size_t elem); + + /** Ensure & get element */ + void* (*ensure_member)( + void *dst, + const char *member); + + /** Return number of elements */ + size_t (*count)( + const void *dst); + + /** Resize to number of elements */ + void (*resize)( + void *dst, + size_t count); +} EcsOpaque; + + +/* Units */ + +/* Helper type to describe translation between two units. Note that this + * is not intended as a generic approach to unit conversions (e.g. from celsius + * to fahrenheit) but to translate between units that derive from the same base + * (e.g. meters to kilometers). + * + * Note that power is applied to the factor. When describing a translation of + * 1000, either use {factor = 1000, power = 1} or {factor = 1, power = 3}. */ +typedef struct ecs_unit_translation_t { + int32_t factor; /**< Factor to apply (e.g. "1000", "1000000", "1024") */ + int32_t power; /**< Power to apply to factor (e.g. "1", "3", "-9") */ +} ecs_unit_translation_t; + +typedef struct EcsUnit { + char *symbol; + ecs_entity_t prefix; /**< Order of magnitude prefix relative to derived */ + ecs_entity_t base; /**< Base unit (e.g. "meters") */ + ecs_entity_t over; /**< Over unit (e.g. "per second") */ + ecs_unit_translation_t translation; /**< Translation for derived unit */ +} EcsUnit; + +typedef struct EcsUnitPrefix { + char *symbol; /**< Symbol of prefix (e.g. "K", "M", "Ki") */ + ecs_unit_translation_t translation; /**< Translation of prefix */ +} EcsUnitPrefix; + + +/* Serializer utilities */ + +typedef enum ecs_meta_type_op_kind_t { + EcsOpArray, + EcsOpVector, + EcsOpOpaque, + EcsOpPush, + EcsOpPop, + + EcsOpScope, /**< Marks last constant that can open/close a scope */ + + EcsOpEnum, + EcsOpBitmask, + + EcsOpPrimitive, /**< Marks first constant that's a primitive */ + + EcsOpBool, + EcsOpChar, + EcsOpByte, + EcsOpU8, + EcsOpU16, + EcsOpU32, + EcsOpU64, + EcsOpI8, + EcsOpI16, + EcsOpI32, + EcsOpI64, + EcsOpF32, + EcsOpF64, + EcsOpUPtr, + EcsOpIPtr, + EcsOpString, + EcsOpEntity, + EcsOpId, + EcsMetaTypeOpKindLast = EcsOpId +} ecs_meta_type_op_kind_t; + +typedef struct ecs_meta_type_op_t { + ecs_meta_type_op_kind_t kind; + ecs_size_t offset; /**< Offset of current field */ + int32_t count; + const char *name; /**< Name of value (only used for struct members) */ + int32_t op_count; /**< Number of operations until next field or end */ + ecs_size_t size; /**< Size of type of operation */ + ecs_entity_t type; /**< Type entity */ + int32_t member_index; /**< Index of member in struct */ + ecs_hashmap_t *members; /**< string -> member index (structs only) */ +} ecs_meta_type_op_t; + +typedef struct EcsMetaTypeSerialized { + ecs_vec_t ops; /**< vector */ +} EcsMetaTypeSerialized; + + +/* Deserializer utilities */ + +#define ECS_META_MAX_SCOPE_DEPTH (32) /* >32 levels of nesting is not sane */ + +typedef struct ecs_meta_scope_t { + ecs_entity_t type; /**< The type being iterated */ + ecs_meta_type_op_t *ops; /**< The type operations (see ecs_meta_type_op_t) */ + int32_t op_count; /**< Number of operations in ops array to process */ + int32_t op_cur; /**< Current operation */ + int32_t elem_cur; /**< Current element (for collections) */ + int32_t prev_depth; /**< Depth to restore, in case dotmember was used */ + void *ptr; /**< Pointer to the value being iterated */ + + const EcsComponent *comp; /**< Pointer to component, in case size/alignment is needed */ + const EcsOpaque *opaque; /**< Opaque type interface */ + ecs_vec_t *vector; /**< Current vector, in case a vector is iterated */ + ecs_hashmap_t *members; /**< string -> member index */ + bool is_collection; /**< Is the scope iterating elements? */ + bool is_inline_array; /**< Is the scope iterating an inline array? */ + bool is_empty_scope; /**< Was scope populated (for collections) */ +} ecs_meta_scope_t; + +/** Type that enables iterating/populating a value using reflection data */ +typedef struct ecs_meta_cursor_t { + const ecs_world_t *world; + ecs_meta_scope_t scope[ECS_META_MAX_SCOPE_DEPTH]; + int32_t depth; + bool valid; + bool is_primitive_scope; /**< If in root scope, this allows for a push for primitive types */ + + /* Custom entity lookup action for overriding default ecs_lookup_fullpath */ + ecs_entity_t (*lookup_action)(const ecs_world_t*, const char*, void*); + void *lookup_ctx; +} ecs_meta_cursor_t; + +FLECS_API +ecs_meta_cursor_t ecs_meta_cursor( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr); + +/** Get pointer to current field */ +FLECS_API +void* ecs_meta_get_ptr( + ecs_meta_cursor_t *cursor); + +/** Move cursor to next field */ +FLECS_API +int ecs_meta_next( + ecs_meta_cursor_t *cursor); + +/** Move cursor to a element */ +FLECS_API +int ecs_meta_elem( + ecs_meta_cursor_t *cursor, + int32_t elem); + +/** Move cursor to member */ +FLECS_API +int ecs_meta_member( + ecs_meta_cursor_t *cursor, + const char *name); + +/** Move cursor to member, supports dot-separated nested members */ +FLECS_API +int ecs_meta_dotmember( + ecs_meta_cursor_t *cursor, + const char *name); + +/** Push a scope (required/only valid for structs & collections) */ +FLECS_API +int ecs_meta_push( + ecs_meta_cursor_t *cursor); + +/** Pop a struct or collection scope (must follow a push) */ +FLECS_API +int ecs_meta_pop( + ecs_meta_cursor_t *cursor); + +/** Is the current scope a collection? */ +FLECS_API +bool ecs_meta_is_collection( + const ecs_meta_cursor_t *cursor); + +/** Get type of current element. */ +FLECS_API +ecs_entity_t ecs_meta_get_type( + const ecs_meta_cursor_t *cursor); + +/** Get unit of current element. */ +FLECS_API +ecs_entity_t ecs_meta_get_unit( + const ecs_meta_cursor_t *cursor); + +/** Get member name of current member */ +FLECS_API +const char* ecs_meta_get_member( + const ecs_meta_cursor_t *cursor); + +/** Get member entity of current member */ +FLECS_API +ecs_entity_t ecs_meta_get_member_id( + const ecs_meta_cursor_t *cursor); + +/* The set functions assign the field with the specified value. If the value + * does not have the same type as the field, it will be cased to the field type. + * If no valid conversion is available, the operation will fail. */ + +/** Set field with boolean value */ +FLECS_API +int ecs_meta_set_bool( + ecs_meta_cursor_t *cursor, + bool value); + +/** Set field with char value */ +FLECS_API +int ecs_meta_set_char( + ecs_meta_cursor_t *cursor, + char value); + +/** Set field with int value */ +FLECS_API +int ecs_meta_set_int( + ecs_meta_cursor_t *cursor, + int64_t value); + +/** Set field with uint value */ +FLECS_API +int ecs_meta_set_uint( + ecs_meta_cursor_t *cursor, + uint64_t value); + +/** Set field with float value */ +FLECS_API +int ecs_meta_set_float( + ecs_meta_cursor_t *cursor, + double value); + +/** Set field with string value */ +FLECS_API +int ecs_meta_set_string( + ecs_meta_cursor_t *cursor, + const char *value); + +/** Set field with string literal value (has enclosing "") */ +FLECS_API +int ecs_meta_set_string_literal( + ecs_meta_cursor_t *cursor, + const char *value); + +/** Set field with entity value */ +FLECS_API +int ecs_meta_set_entity( + ecs_meta_cursor_t *cursor, + ecs_entity_t value); + +/** Set field with (component) id value */ +FLECS_API +int ecs_meta_set_id( + ecs_meta_cursor_t *cursor, + ecs_id_t value); + +/** Set field with (component) id value */ +FLECS_API +int ecs_meta_set_component( + ecs_meta_cursor_t *cursor, + ecs_id_t value); + +/** Set field with null value */ +FLECS_API +int ecs_meta_set_null( + ecs_meta_cursor_t *cursor); + +/** Set field with dynamic value */ +FLECS_API +int ecs_meta_set_value( + ecs_meta_cursor_t *cursor, + const ecs_value_t *value); + +/* Functions for getting members. */ + +/** Get field value as boolean. */ +FLECS_API +bool ecs_meta_get_bool( + const ecs_meta_cursor_t *cursor); + +/** Get field value as char. */ +FLECS_API +char ecs_meta_get_char( + const ecs_meta_cursor_t *cursor); + +/** Get field value as signed integer. */ +FLECS_API +int64_t ecs_meta_get_int( + const ecs_meta_cursor_t *cursor); + +/** Get field value as unsigned integer. */ +FLECS_API +uint64_t ecs_meta_get_uint( + const ecs_meta_cursor_t *cursor); + +/** Get field value as float. */ +FLECS_API +double ecs_meta_get_float( + const ecs_meta_cursor_t *cursor); + +/** Get field value as string. + * This operation does not perform conversions. If the field is not a string, + * this operation will fail. + */ +FLECS_API +const char* ecs_meta_get_string( + const ecs_meta_cursor_t *cursor); + +/** Get field value as entity. + * This operation does not perform conversions. */ +FLECS_API +ecs_entity_t ecs_meta_get_entity( + const ecs_meta_cursor_t *cursor); + +/** Get field value as (component) id. + * This operation can convert from an entity. */ +ecs_id_t ecs_meta_get_id( + const ecs_meta_cursor_t *cursor); + +/** Convert pointer of primitive kind to float. */ +FLECS_API +double ecs_meta_ptr_to_float( + ecs_primitive_kind_t type_kind, + const void *ptr); + +/* API functions for creating meta types */ + +/** Used with ecs_primitive_init(). */ +typedef struct ecs_primitive_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional) */ + ecs_primitive_kind_t kind; +} ecs_primitive_desc_t; + +/** Create a new primitive type */ +FLECS_API +ecs_entity_t ecs_primitive_init( + ecs_world_t *world, + const ecs_primitive_desc_t *desc); + +/** Used with ecs_enum_init(). */ +typedef struct ecs_enum_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional) */ + ecs_enum_constant_t constants[ECS_MEMBER_DESC_CACHE_SIZE]; +} ecs_enum_desc_t; + +/** Create a new enum type */ +FLECS_API +ecs_entity_t ecs_enum_init( + ecs_world_t *world, + const ecs_enum_desc_t *desc); + + +/** Used with ecs_bitmask_init(). */ +typedef struct ecs_bitmask_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional) */ + ecs_bitmask_constant_t constants[ECS_MEMBER_DESC_CACHE_SIZE]; +} ecs_bitmask_desc_t; + +/** Create a new bitmask type */ +FLECS_API +ecs_entity_t ecs_bitmask_init( + ecs_world_t *world, + const ecs_bitmask_desc_t *desc); + + +/** Used with ecs_array_init(). */ +typedef struct ecs_array_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional) */ + ecs_entity_t type; + int32_t count; +} ecs_array_desc_t; + +/** Create a new array type */ +FLECS_API +ecs_entity_t ecs_array_init( + ecs_world_t *world, + const ecs_array_desc_t *desc); + + +/** Used with ecs_vector_init(). */ +typedef struct ecs_vector_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional) */ + ecs_entity_t type; +} ecs_vector_desc_t; + +/** Create a new vector type */ +FLECS_API +ecs_entity_t ecs_vector_init( + ecs_world_t *world, + const ecs_vector_desc_t *desc); + + +/** Used with ecs_struct_init(). */ +typedef struct ecs_struct_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional) */ + ecs_member_t members[ECS_MEMBER_DESC_CACHE_SIZE]; +} ecs_struct_desc_t; + +/** Create a new struct type */ +FLECS_API +ecs_entity_t ecs_struct_init( + ecs_world_t *world, + const ecs_struct_desc_t *desc); + +/** Used with ecs_opaque_init(). */ +typedef struct ecs_opaque_desc_t { + ecs_entity_t entity; + EcsOpaque type; +} ecs_opaque_desc_t; + +/** Create a new opaque type. + * Opaque types are types of which the layout doesn't match what can be modelled + * with the primitives of the meta framework, but which have a structure + * that can be described with meta primitives. Typical examples are STL types + * such as std::string or std::vector, types with a nontrivial layout, and types + * that only expose getter/setter methods. + * + * An opaque type is a combination of a serialization function, and a handle to + * a meta type which describes the structure of the serialized output. For + * example, an opaque type for std::string would have a serializer function that + * accesses .c_str(), and with type ecs_string_t. + * + * The serializer callback accepts a serializer object and a pointer to the + * value of the opaque type to be serialized. The serializer has two methods: + * + * - value, which serializes a value (such as .c_str()) + * - member, which specifies a member to be serialized (in the case of a struct) + */ +FLECS_API +ecs_entity_t ecs_opaque_init( + ecs_world_t *world, + const ecs_opaque_desc_t *desc); + +/** Used with ecs_unit_init(). */ +typedef struct ecs_unit_desc_t { + /** Existing entity to associate with unit (optional) */ + ecs_entity_t entity; + + /** Unit symbol, e.g. "m", "%", "g". (optional) */ + const char *symbol; + + /** Unit quantity, e.g. distance, percentage, weight. (optional) */ + ecs_entity_t quantity; + + /** Base unit, e.g. "meters" (optional) */ + ecs_entity_t base; + + /** Over unit, e.g. "per second" (optional) */ + ecs_entity_t over; + + /** Translation to apply to derived unit (optional) */ + ecs_unit_translation_t translation; + + /** Prefix indicating order of magnitude relative to the derived unit. If set + * together with "translation", the values must match. If translation is not + * set, setting prefix will auto-populate it. + * Additionally, setting the prefix will enforce that the symbol (if set) + * is consistent with the prefix symbol + symbol of the derived unit. If the + * symbol is not set, it will be auto populated. */ + ecs_entity_t prefix; +} ecs_unit_desc_t; + +/** Create a new unit */ +FLECS_API +ecs_entity_t ecs_unit_init( + ecs_world_t *world, + const ecs_unit_desc_t *desc); + +/** Used with ecs_unit_prefix_init(). */ +typedef struct ecs_unit_prefix_desc_t { + /** Existing entity to associate with unit prefix (optional) */ + ecs_entity_t entity; + + /** Unit symbol, e.g. "m", "%", "g". (optional) */ + const char *symbol; + + /** Translation to apply to derived unit (optional) */ + ecs_unit_translation_t translation; +} ecs_unit_prefix_desc_t; + +/** Create a new unit prefix */ +FLECS_API +ecs_entity_t ecs_unit_prefix_init( + ecs_world_t *world, + const ecs_unit_prefix_desc_t *desc); + +/** Create a new quantity */ +FLECS_API +ecs_entity_t ecs_quantity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc); + +/* Convenience macros */ + +#define ecs_primitive(world, ...)\ + ecs_primitive_init(world, &(ecs_primitive_desc_t) __VA_ARGS__ ) + +#define ecs_enum(world, ...)\ + ecs_enum_init(world, &(ecs_enum_desc_t) __VA_ARGS__ ) + +#define ecs_bitmask(world, ...)\ + ecs_bitmask_init(world, &(ecs_bitmask_desc_t) __VA_ARGS__ ) + +#define ecs_array(world, ...)\ + ecs_array_init(world, &(ecs_array_desc_t) __VA_ARGS__ ) + +#define ecs_vector(world, ...)\ + ecs_vector_init(world, &(ecs_vector_desc_t) __VA_ARGS__ ) + +#define ecs_opaque(world, ...)\ + ecs_opaque_init(world, &(ecs_opaque_desc_t) __VA_ARGS__ ) + +#define ecs_struct(world, ...)\ + ecs_struct_init(world, &(ecs_struct_desc_t) __VA_ARGS__ ) + +#define ecs_unit(world, ...)\ + ecs_unit_init(world, &(ecs_unit_desc_t) __VA_ARGS__ ) + +#define ecs_unit_prefix(world, ...)\ + ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) __VA_ARGS__ ) + +#define ecs_quantity(world, ...)\ + ecs_quantity_init(world, &(ecs_entity_desc_t) __VA_ARGS__ ) + +/* Module import */ +FLECS_API +void FlecsMetaImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_EXPR +#ifdef FLECS_NO_EXPR +#error "FLECS_NO_EXPR failed: EXPR is required by other addons" +#endif +/** + * @file addons/expr.h + * @brief Flecs expression parser addon. + * + * Parse expression strings into component values. The notation is similar to + * JSON but with a smaller footprint, native support for (large) integer types, + * character types, enumerations, bitmasks and entity identifiers. + * + * Examples: + * + * Member names: + * + * {x: 10, y: 20} + * + * No member names (uses member ordering): + * + * {10, 20} + * + * Enum values: + * + * {color: Red} + * + * Bitmask values: + * + * {toppings: Lettuce|Tomato} + * + * Collections: + * + * {points: [10, 20, 30]} + * + * Nested objects: + * + * {start: {x: 10, y: 20}, stop: {x: 30, y: 40}} + * + */ + +#ifdef FLECS_EXPR + +#ifndef FLECS_META +#define FLECS_META +#endif + +#ifndef FLECS_PARSER +#define FLECS_PARSER +#endif + +#ifndef FLECS_EXPR_H +#define FLECS_EXPR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup c_addons_expr Expr + * @ingroup c_addons + * Serialize/deserialize values to string. + * + * @{ + */ + +/** Write an escaped character. + * Write a character to an output string, insert escape character if necessary. + * + * @param out The string to write the character to. + * @param in The input character. + * @param delimiter The delimiter used (for example '"') + * @return Pointer to the character after the last one written. + */ +FLECS_API +char* ecs_chresc( + char *out, + char in, + char delimiter); + +/** Parse an escaped character. + * Parse a character with a potential escape sequence. + * + * @param in Pointer to character in input string. + * @param out Output string. + * @return Pointer to the character after the last one read. + */ +const char* ecs_chrparse( + const char *in, + char *out); + +/** Write an escaped string. + * Write an input string to an output string, escape characters where necessary. + * To determine the size of the output string, call the operation with a NULL + * argument for 'out', and use the returned size to allocate a string that is + * large enough. + * + * @param out Pointer to output string (must be). + * @param size Maximum number of characters written to output. + * @param delimiter The delimiter used (for example '"'). + * @param in The input string. + * @return The number of characters that (would) have been written. + */ +FLECS_API +ecs_size_t ecs_stresc( + char *out, + ecs_size_t size, + char delimiter, + const char *in); + +/** Return escaped string. + * Return escaped version of input string. Same as ecs_stresc(), but returns an + * allocated string of the right size. + * + * @param delimiter The delimiter used (for example '"'). + * @param in The input string. + * @return Escaped string. + */ +FLECS_API +char* ecs_astresc( + char delimiter, + const char *in); + +/** Storage for parser variables. Variables make it possible to parameterize + * expression strings, and are referenced with the $ operator (e.g. $var). */ +typedef struct ecs_expr_var_t { + char *name; + ecs_value_t value; + bool owned; /* Set to false if ecs_vars_t should not take ownership of var */ +} ecs_expr_var_t; + +typedef struct ecs_expr_var_scope_t { + ecs_hashmap_t var_index; + ecs_vec_t vars; + struct ecs_expr_var_scope_t *parent; +} ecs_expr_var_scope_t; + +typedef struct ecs_vars_t { + ecs_world_t *world; + ecs_expr_var_scope_t root; + ecs_expr_var_scope_t *cur; +} ecs_vars_t; + +/** Init variable storage */ +FLECS_API +void ecs_vars_init( + ecs_world_t *world, + ecs_vars_t *vars); + +/** Cleanup variable storage */ +FLECS_API +void ecs_vars_fini( + ecs_vars_t *vars); + +/** Push variable scope */ +FLECS_API +void ecs_vars_push( + ecs_vars_t *vars); + +/** Pop variable scope */ +FLECS_API +int ecs_vars_pop( + ecs_vars_t *vars); + +/** Declare variable in current scope */ +FLECS_API +ecs_expr_var_t* ecs_vars_declare( + ecs_vars_t *vars, + const char *name, + ecs_entity_t type); + +/** Declare variable in current scope from value. + * This operation takes ownership of the value. The value pointer must be + * allocated with ecs_value_new(). + */ +FLECS_API +ecs_expr_var_t* ecs_vars_declare_w_value( + ecs_vars_t *vars, + const char *name, + ecs_value_t *value); + +/** Lookup variable in scope and parent scopes */ +FLECS_API +ecs_expr_var_t* ecs_vars_lookup( + const ecs_vars_t *vars, + const char *name); + +/** Used with ecs_parse_expr(). */ +typedef struct ecs_parse_expr_desc_t { + const char *name; + const char *expr; + ecs_entity_t (*lookup_action)( + const ecs_world_t*, + const char *value, + void *ctx); + void *lookup_ctx; + ecs_vars_t *vars; +} ecs_parse_expr_desc_t; + +/** Parse expression into value. + * This operation parses a flecs expression into the provided pointer. The + * memory pointed to must be large enough to contain a value of the used type. + * + * If no type and pointer are provided for the value argument, the operation + * will discover the type from the expression and allocate storage for the + * value. The allocated value must be freed with ecs_value_free(). + * + * @param world The world. + * @param ptr The pointer to the expression to parse. + * @param value The value containing type & pointer to write to. + * @param desc Configuration parameters for deserializer. + * @return Pointer to the character after the last one read, or NULL if failed. + */ +FLECS_API +const char* ecs_parse_expr( + ecs_world_t *world, + const char *ptr, + ecs_value_t *value, + const ecs_parse_expr_desc_t *desc); + +/** Serialize value into expression string. + * This operation serializes a value of the provided type to a string. The + * memory pointed to must be large enough to contain a value of the used type. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @return String with expression, or NULL if failed. + */ +FLECS_API +char* ecs_ptr_to_expr( + const ecs_world_t *world, + ecs_entity_t type, + const void *data); + +/** Serialize value into expression buffer. + * Same as ecs_ptr_to_expr(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param buf The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_ptr_to_expr_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + ecs_strbuf_t *buf); + +/** Similar as ecs_ptr_to_expr(), but serializes values to string. + * Whereas the output of ecs_ptr_to_expr() is a valid expression, the output of + * ecs_ptr_to_str() is a string representation of the value. In most cases the + * output of the two operations is the same, but there are some differences: + * - Strings are not quoted + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @return String with result, or NULL if failed. + */ +FLECS_API +char* ecs_ptr_to_str( + const ecs_world_t *world, + ecs_entity_t type, + const void *data); + +/** Serialize value into string buffer. + * Same as ecs_ptr_to_str(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param buf The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_ptr_to_str_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + ecs_strbuf_t *buf); + +/** Serialize primitive value into string buffer. + * Serializes a primitive value to an ecs_strbuf_t instance. This operation can + * be reused by other serializers to avoid having to write boilerplate code that + * serializes primitive values to a string. + * + * @param world The world. + * @param kind The kind of primitive value. + * @param data The value to serialize + * @param buf The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_primitive_to_expr_buf( + const ecs_world_t *world, + ecs_primitive_kind_t kind, + const void *data, + ecs_strbuf_t *buf); + +/** Parse expression token. + * Expression tokens can contain more characters (such as '|') than tokens + * parsed by the query (term) parser. + * + * @param name The name of the expression (used for debug logs). + * @param expr The full expression (used for debug logs). + * @param ptr The pointer to the expression to parse. + * @param token The buffer to write to (must have size ECS_MAX_TOKEN_SIZE) + * @return Pointer to the character after the last one read, or NULL if failed. + */ +FLECS_API +const char *ecs_parse_expr_token( + const char *name, + const char *expr, + const char *ptr, + char *token); + +/** Evaluate interpolated expressions in string. + * This operation evaluates expressions in a string, and replaces them with + * their evaluated result. Supported expression formats are: + * - $variable_name + * - {expression} + * + * The $, { and } characters can be escaped with a backslash (\). + * + * @param world The world. + * @param str The string to evaluate. + * @param vars The variables to use for evaluation. + */ +FLECS_API +char* ecs_interpolate_string( + ecs_world_t *world, + const char *str, + const ecs_vars_t *vars); + +/** Convert iterator to vars + * This operation converts an iterator to a variable array. This allows for + * using iterator results in expressions. The operation only converts a + * single result at a time, and does not progress the iterator. + * + * Iterator fields with data will be made available as variables with as name + * the field index (e.g. "$1"). The operation does not check if reflection data + * is registered for a field type. If no reflection data is registered for the + * type, using the field variable in expressions will fail. + * + * Field variables will only contain single elements, even if the iterator + * returns component arrays. The offset parameter can be used to specify which + * element in the component arrays to return. The offset parameter must be + * smaller than it->count. + * + * The operation will create a variable for query variables that contain a + * single entity. + * + * The operation will attempt to use existing variables. If a variable does not + * yet exist, the operation will create it. If an existing variable exists with + * a mismatching type, the operation will fail. + * + * Accessing variables after progressing the iterator or after the iterator is + * destroyed will result in undefined behavior. + * + * If vars contains a variable that is not present in the iterator, the variable + * will not be modified. + * + * @param it The iterator to convert to variables. + * @param vars The variables to write to. + * @param offset The offset to the current element. + */ +FLECS_API +void ecs_iter_to_vars( + const ecs_iter_t *it, + ecs_vars_t *vars, + int offset); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +#endif + +#ifdef FLECS_META_C +#ifdef FLECS_NO_META_C +#error "FLECS_NO_META_C failed: META_C is required by other addons" +#endif +/** + * @file addons/meta_c.h + * @brief Utility macros for populating reflection data in C. + */ + +#ifdef FLECS_META_C + +/** + * @defgroup c_addons_meta_c Meta Utilities + * @ingroup c_addons + * Macro utilities to automatically insert reflection data. + * + * @{ + */ + +#ifndef FLECS_META +#define FLECS_META +#endif + +#ifndef FLECS_PARSER +#define FLECS_PARSER +#endif + +#ifndef FLECS_META_C_H +#define FLECS_META_C_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Macro that controls behavior of API. Usually set in module header. When the + * macro is not defined, it defaults to IMPL. */ + +/* Define variables used by reflection utilities. This should only be defined + * by the module itself, not by the code importing the module */ +/* #define ECS_META_IMPL IMPL */ + +/* Don't define variables used by reflection utilities but still declare the + * variable for the component id. This enables the reflection utilities to be + * used for global component variables, even if no reflection is used. */ +/* #define ECS_META_IMPL DECLARE */ + +/* Don't define variables used by reflection utilities. This generates an extern + * variable for the component identifier. */ +/* #define ECS_META_IMPL EXTERN */ + +/** Declare component with descriptor */ +#define ECS_META_COMPONENT(world, name)\ + ECS_COMPONENT_DEFINE(world, name);\ + ecs_meta_from_desc(world, ecs_id(name),\ + FLECS__##name##_kind, FLECS__##name##_desc) + +/** ECS_STRUCT(name, body) */ +#define ECS_STRUCT(name, ...)\ + ECS_META_IMPL_CALL(ECS_STRUCT_, ECS_META_IMPL, name, #__VA_ARGS__);\ + ECS_STRUCT_TYPE(name, __VA_ARGS__) + +/** ECS_ENUM(name, body) */ +#define ECS_ENUM(name, ...)\ + ECS_META_IMPL_CALL(ECS_ENUM_, ECS_META_IMPL, name, #__VA_ARGS__);\ + ECS_ENUM_TYPE(name, __VA_ARGS__) + +/** ECS_BITMASK(name, body) */ +#define ECS_BITMASK(name, ...)\ + ECS_META_IMPL_CALL(ECS_BITMASK_, ECS_META_IMPL, name, #__VA_ARGS__);\ + ECS_ENUM_TYPE(name, __VA_ARGS__) + +/** Macro used to mark part of type for which no reflection data is created */ +#define ECS_PRIVATE + +/** Populate meta information from type descriptor. */ +FLECS_API +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc); + + +/* Private API */ + +/* Utilities to switch between IMPL, DECLARE and EXTERN variants */ +#define ECS_META_IMPL_CALL_INNER(base, impl, name, type_desc)\ + base ## impl(name, type_desc) + +#define ECS_META_IMPL_CALL(base, impl, name, type_desc)\ + ECS_META_IMPL_CALL_INNER(base, impl, name, type_desc) + +/* ECS_STRUCT implementation */ +#define ECS_STRUCT_TYPE(name, ...)\ + typedef struct __VA_ARGS__ name + +#define ECS_STRUCT_ECS_META_IMPL ECS_STRUCT_IMPL + +#define ECS_STRUCT_IMPL(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + static const char *FLECS__##name##_desc = type_desc;\ + static ecs_type_kind_t FLECS__##name##_kind = EcsStructType;\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_STRUCT_DECLARE(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_STRUCT_EXTERN(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name) + + +/* ECS_ENUM implementation */ +#define ECS_ENUM_TYPE(name, ...)\ + typedef enum __VA_ARGS__ name + +#define ECS_ENUM_ECS_META_IMPL ECS_ENUM_IMPL + +#define ECS_ENUM_IMPL(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + static const char *FLECS__##name##_desc = type_desc;\ + static ecs_type_kind_t FLECS__##name##_kind = EcsEnumType;\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_ENUM_DECLARE(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_ENUM_EXTERN(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name) + + +/* ECS_BITMASK implementation */ +#define ECS_BITMASK_TYPE(name, ...)\ + typedef enum __VA_ARGS__ name + +#define ECS_BITMASK_ECS_META_IMPL ECS_BITMASK_IMPL + +#define ECS_BITMASK_IMPL(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + static const char *FLECS__##name##_desc = type_desc;\ + static ecs_type_kind_t FLECS__##name##_kind = EcsBitmaskType;\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_BITMASK_DECLARE(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_BITMASK_EXTERN(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name) + +#ifdef __cplusplus +} +#endif + +#endif // FLECS_META_C_H + +/** @} */ + +#endif // FLECS_META_C + +#endif + +#ifdef FLECS_PLECS +#ifdef FLECS_NO_PLECS +#error "FLECS_NO_PLECS failed: PLECS is required by other addons" +#endif +/** + * @file addons/plecs.h + * @brief Flecs script module. + * + * For script, see examples/plecs. + */ + +#ifdef FLECS_PLECS + +/** + * @defgroup c_addons_plecs Flecs script + * @ingroup c_addons + * Data definition format for loading entity data. + * + * @{ + */ + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_PARSER +#define FLECS_PARSER +#endif + +#ifndef FLECS_EXPR +#define FLECS_EXPR +#endif + +#ifndef FLECS_PLECS_H +#define FLECS_PLECS_H + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API +extern ECS_COMPONENT_DECLARE(EcsScript); + +/* Script component */ +typedef struct EcsScript { + ecs_vec_t using_; + char *script; + ecs_vec_t prop_defaults; + ecs_world_t *world; +} EcsScript; + +/** Parse plecs string. + * This parses a plecs string and instantiates the entities in the world. + * + * @param world The world. + * @param name The script name (typically the file). + * @param str The plecs string. + * @return Zero if success, non-zero otherwise. + */ +FLECS_API +int ecs_plecs_from_str( + ecs_world_t *world, + const char *name, + const char *str); + +/** Parse plecs file. + * This parses a plecs file and instantiates the entities in the world. This + * operation is equivalent to loading the file contents and passing it to + * ecs_plecs_from_str(). + * + * @param world The world. + * @param filename The plecs file name. + * @return Zero if success, non-zero otherwise. + */ +FLECS_API +int ecs_plecs_from_file( + ecs_world_t *world, + const char *filename); + +/** Used with ecs_script_init() */ +typedef struct ecs_script_desc_t { + ecs_entity_t entity; /* Set to customize entity handle associated with script */ + const char *filename; /* Set to load script from file */ + const char *str; /* Set to parse script from string */ +} ecs_script_desc_t; + +/** Load managed script. + * A managed script tracks which entities it creates, and keeps those entities + * synchronized when the contents of the script are updated. When the script is + * updated, entities that are no longer in the new version will be deleted. + * + * This feature is experimental. + * + * @param world The world. + * @param desc Script descriptor. + */ +FLECS_API +ecs_entity_t ecs_script_init( + ecs_world_t *world, + const ecs_script_desc_t *desc); + +#define ecs_script(world, ...)\ + ecs_script_init(world, &(ecs_script_desc_t) __VA_ARGS__) + +/** Update script with new code. + * + * @param world The world. + * @param script The script entity. + * @param instance An assembly instance (optional). + * @param str The script code. + * @param vars Optional preset variables for script parameterization. + */ +FLECS_API +int ecs_script_update( + ecs_world_t *world, + ecs_entity_t script, + ecs_entity_t instance, + const char *str, + ecs_vars_t *vars); + +/** Clear all entities associated with script. + * + * @param world The world. + * @param script The script entity. + * @param instance The script instance. + */ +FLECS_API +void ecs_script_clear( + ecs_world_t *world, + ecs_entity_t script, + ecs_entity_t instance); + +/* Module import */ +FLECS_API +void FlecsScriptImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_RULES +#ifdef FLECS_NO_RULES +#error "FLECS_NO_RULES failed: RULES is required by other addons" +#endif +/** + * @file addons/rules.h + * @brief Rule query engine addon. + * + * Rules are advanced queries that in addition to the capabilities of regular + * queries and filters have the following features: + * + * - query for all components of an entity (vs. all entities for a component) + * - query for all relationship pairs of an entity + * - support for query variables that are resolved at evaluation time + * - automatic traversal of transitive relationships + */ + +#ifdef FLECS_RULES + +/** + * @defgroup c_addons_rules Rules + * @ingroup c_addons + * Rules are an advanced query engine for matching against entity graphs. + * + * @{ + */ + +#ifndef FLECS_RULES_H +#define FLECS_RULES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Convenience macro for rule creation */ +#define ecs_rule(world, ...)\ + ecs_rule_init(world, &(ecs_filter_desc_t) __VA_ARGS__ ) + +/** Create a rule. + * A rule accepts the same descriptor as a filter, but has the additional + * ability to use query variables. + * + * Query variables can be used to constrain wildcards across multiple terms to + * the same entity. Regular ECS queries do this in a limited form, as querying + * for Position, Velocity only returns entities that have both components. + * + * Query variables expand this to constrain entities that are resolved while the + * query is being matched. Consider a query for all entities and the mission + * they are on: + * (Mission, *) + * + * If an entity is on multiple missions, the wildcard will match it multiple + * times. Now say we want to only list combat missions. Naively we could try: + * (Mission, *), CombatMission(*) + * + * But this doesn't work, as term 1 returns entities with missions, and term 2 + * returns all combat missions for all entities. Query variables make it + * possible to apply CombatMission to the found mission: + * (Mission, $M), CombatMission($M) + * + * By using the same variable ('M') we ensure that CombatMission is applied to + * the mission found in the current result. + * + * Variables can be used in each part of the term (predicate, subject, object). + * This is a valid query: + * Likes($X, $Y), Likes($Y, $X) + * + * This is also a valid query: + * _Component, Serializable(_Component) + * + * In the query expression syntax, variables are prefixed with a $. When using + * the descriptor, specify the variable kind: + * desc.terms[0].second = { .name = "X", .var = EcsVarIsVariable } + * + * Different terms with the same variable name are automatically correlated by + * the query engine. + * + * A rule needs to be explicitly deleted with ecs_rule_fini(). + * + * @param world The world. + * @param desc The descriptor (see ecs_filter_desc_t) + * @return The rule. + */ +FLECS_API +ecs_rule_t* ecs_rule_init( + ecs_world_t *world, + const ecs_filter_desc_t *desc); + +/** Delete a rule. + * + * @param rule The rule. + */ +FLECS_API +void ecs_rule_fini( + ecs_rule_t *rule); + +/** Obtain filter from rule. + * This operation returns the filter with which the rule was created. + * + * @param rule The rule. + * @return The filter. + */ +FLECS_API +const ecs_filter_t* ecs_rule_get_filter( + const ecs_rule_t *rule); + +/** Return number of variables in rule. + * + * @param rule The rule. + * @return The number of variables/ + */ +FLECS_API +int32_t ecs_rule_var_count( + const ecs_rule_t *rule); + +/** Find variable index. + * This operation looks up the index of a variable in the rule. This index can + * be used in operations like ecs_iter_set_var() and ecs_iter_get_var(). + * + * @param rule The rule. + * @param name The variable name. + * @return The variable index. + */ +FLECS_API +int32_t ecs_rule_find_var( + const ecs_rule_t *rule, + const char *name); + +/** Get variable name. + * This operation returns the variable name for an index. + * + * @param rule The rule. + * @param var_id The variable index. + */ +FLECS_API +const char* ecs_rule_var_name( + const ecs_rule_t *rule, + int32_t var_id); + +/** Test if variable is an entity. + * Internally the rule engine has entity variables and table variables. When + * iterating through rule variables (by using ecs_rule_variable_count()) only + * the values for entity variables are accessible. This operation enables an + * application to check if a variable is an entity variable. + * + * @param rule The rule. + * @param var_id The variable id. + */ +FLECS_API +bool ecs_rule_var_is_entity( + const ecs_rule_t *rule, + int32_t var_id); + +/** Iterate a rule. + * Note that rule iterators may allocate memory, and that unless the iterator + * is iterated until completion, it may still hold resources. When stopping + * iteration before ecs_rule_next() has returned false, use ecs_iter_fini() to + * cleanup any remaining resources. + * + * @param world The world. + * @param rule The rule. + * @return An iterator. + */ +FLECS_API +ecs_iter_t ecs_rule_iter( + const ecs_world_t *world, + const ecs_rule_t *rule); + +/** Progress rule iterator. + * + * @param it The iterator. + */ +FLECS_API +bool ecs_rule_next( + ecs_iter_t *it); + +/** Progress instanced iterator. + * Should not be called unless you know what you're doing :-) + * + * @param it The iterator. + */ +FLECS_API +bool ecs_rule_next_instanced( + ecs_iter_t *it); + +/** Convert rule to a string. + * This will convert the rule program to a string which can aid in debugging + * the behavior of a rule. + * + * The returned string must be freed with ecs_os_free(). + * + * @param rule The rule. + * @return The string + */ +FLECS_API +char* ecs_rule_str( + const ecs_rule_t *rule); + +/** Convert rule to string with profile. + * To use this you must set the EcsIterProfile flag on an iterator before + * starting iteration: + * it.flags |= EcsIterProfile + * + * @param rule The rule. + * @return The string + */ +FLECS_API +char* ecs_rule_str_w_profile( + const ecs_rule_t *rule, + const ecs_iter_t *it); + +/** Populate variables from key-value string. + * Convenience function to set rule variables from a key-value string separated + * by comma's. The string must have the following format: + * var_a: value, var_b: value + * + * The key-value list may optionally be enclosed in parenthesis. + * + * @param rule The rule. + * @param it The iterator for which to set the variables. + * @param expr The key-value expression. + */ +FLECS_API +const char* ecs_rule_parse_vars( + ecs_rule_t *rule, + ecs_iter_t *it, + const char *expr); + +#ifdef __cplusplus +} +#endif + +#endif // FLECS_RULES_H + +/** @} */ + +#endif // FLECS_RULES + +#endif + +#ifdef FLECS_SNAPSHOT +#ifdef FLECS_NO_SNAPSHOT +#error "FLECS_NO_SNAPSHOT failed: SNAPSHOT is required by other addons" +#endif +/** + * @file addons/snapshot.h + * @brief Snapshot addon. + * + * A snapshot records the state of a world in a way so that it can be restored + * later. Snapshots work with POD components and non-POD components, provided + * that the appropriate lifecycle actions are registered for non-POD components. + * + * A snapshot is tightly coupled to a world. It is not possible to restore a + * snapshot from world A into world B. + */ + +#ifdef FLECS_SNAPSHOT + +/** + * @defgroup c_addons_snapshot Snapshot + * @ingroup c_addons + * @brief Save & restore world. + * + * @{ + */ + +#ifndef FLECS_SNAPSHOT_H +#define FLECS_SNAPSHOT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** A snapshot stores the state of a world in a particular point in time. */ +typedef struct ecs_snapshot_t ecs_snapshot_t; + +/** Create a snapshot. + * This operation makes a copy of the current state of the world. + * + * @param world The world to snapshot. + * @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. + * @return The snapshot. + */ +FLECS_API +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter); + +/** 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); + +/** 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 snapshot The snapshot to free. + */ +FLECS_API +void ecs_snapshot_free( + ecs_snapshot_t *snapshot); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_PARSER +#ifdef FLECS_NO_PARSER +#error "FLECS_NO_PARSER failed: PARSER is required by other addons" +#endif +/** + * @file addons/parser.h + * @brief Parser addon. + * + * The parser addon parses string expressions into lists of terms, and can be + * used to construct filters, queries and types. + */ + +#ifdef FLECS_PARSER + +/** + * @defgroup c_addons_parser Parser + * @ingroup c_addons + * Query DSL parser and parsing utilities. + * + * @{ + */ + +#ifndef FLECS_PARSER_H +#define FLECS_PARSER_H + +/** Maximum number of extra arguments in term expression */ +#define ECS_PARSER_MAX_ARGS (16) + +#ifdef __cplusplus +extern "C" { +#endif + +/** Skip whitespace characters. + * This function skips whitespace characters. Does not skip newlines. + * + * @param ptr Pointer to (potential) whitespaces to skip. + * @return Pointer to the next non-whitespace character. + */ +FLECS_API +const char* ecs_parse_ws( + const char *ptr); + +/** Skip whitespace and newline characters. + * This function skips whitespace characters. + * + * @param ptr Pointer to (potential) whitespaces to skip. + * @return Pointer to the next non-whitespace character. + */ +FLECS_API +const char* ecs_parse_ws_eol( + const char *ptr); + +/** Utility function to parse an identifier */ +const char* ecs_parse_identifier( + const char *name, + const char *expr, + const char *ptr, + char *token_out); + +/** Parse digit. + * This function will parse until the first non-digit character is found. The + * provided expression must contain at least one digit character. + * + * @param ptr The expression to parse. + * @param token The output buffer. + * @return Pointer to the first non-digit character. + */ +FLECS_API +const char* ecs_parse_digit( + const char *ptr, + char *token); + +/** Parse a single token. + * This function can be used as simple tokenizer by other parsers. + * + * @param name of program (used for logging). + * @param expr pointer to token to parse. + * @param ptr pointer to first character to parse. + * @param token_out Parsed token (buffer should be ECS_MAX_TOKEN_SIZE large) + * @return Pointer to the next token, or NULL if error occurred. + */ +FLECS_API +const char* ecs_parse_token( + const char *name, + const char *expr, + const char *ptr, + char *token_out, + char delim); + +/** Parse term in expression. + * This operation parses a single term in an expression and returns a pointer + * to the next term expression. + * + * If the returned pointer points to the 0-terminator, the expression is fully + * parsed. The function would typically be called in a while loop: + * + * @code + * const char *ptr = expr; + * while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { } + * @endcode + * + * The operation does not attempt to find entity ids from the names in the + * expression. Use the ecs_term_resolve_ids() function to resolve the identifiers + * in the parsed term. + * + * The returned term will in most cases contain allocated resources, which + * should freed (or used) by the application. To free the resources for a term, + * use the ecs_term_free() function. + * + * The parser accepts expressions in the legacy string format. + * + * @param world The world. + * @param name The name of the expression (optional, improves error logs) + * @param expr The expression to parse (optional, improves error logs) + * @param ptr The pointer to the current term (must be in expr). + * @param term_out Out parameter for the term. + * @param extra_args Out array for extra args, must be of size ECS_PARSER_MAX_ARGS. + * @return pointer to next term if successful, NULL if failed. + */ +FLECS_API +char* ecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term_out, + ecs_oper_kind_t *extra_oper, + ecs_term_id_t *extra_args, + bool allow_newline); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // FLECS_PARSER_H + +/** @} */ + +#endif // FLECS_PARSER + +#endif + +#ifdef FLECS_OS_API_IMPL +#ifdef FLECS_NO_OS_API_IMPL +#error "FLECS_NO_OS_API_IMPL failed: OS_API_IMPL is required by other addons" +#endif +/** + * @file addons/os_api_impl.h + * @brief Default OS API implementation. + */ + +#ifdef FLECS_OS_API_IMPL + +/** + * @defgroup c_addons_os_api_impl OS API Implementation + * @ingroup c_addons + * Default implementation for OS API interface. + * + * @{ + */ + +#ifndef FLECS_OS_API_IMPL_H +#define FLECS_OS_API_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API +void ecs_set_os_api_impl(void); + +#ifdef __cplusplus +} +#endif + +#endif // FLECS_OS_API_IMPL_H + +/** @} */ + +#endif // FLECS_OS_API_IMPL + +#endif + +#ifdef FLECS_MODULE +#ifdef FLECS_NO_MODULE +#error "FLECS_NO_MODULE failed: MODULE is required by other addons" +#endif +/** + * @file addons/module.h + * @brief Module addon. + * + * The module addon allows for creating and importing modules. Flecs modules + * enable applications to organize components and systems into reusable units of + * code that can easily be across projects. + */ + +#ifdef FLECS_MODULE + +/** + * @defgroup c_addons_module Module + * @ingroup c_addons + * Modules organize components, systems and more in reusable units of code. + * + * @{ + */ + +#ifndef FLECS_MODULE_H +#define FLECS_MODULE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** 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 import function. + * @param module_name The name of the module. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name); + +/** Same as ecs_import(), but with name to scope conversion. + * PascalCase names are automatically converted to scoped names. + * + * @param world The world. + * @param module The module import function. + * @param module_name_c The name of the module. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import_c( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name_c); + +/** 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. + */ +FLECS_API +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name); + +/** Register a new module. */ +FLECS_API +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const char *c_name, + const ecs_component_desc_t *desc); + +/** Define module. */ +#define ECS_MODULE_DEFINE(world, id)\ + {\ + ecs_component_desc_t desc = {0};\ + desc.entity = ecs_id(id);\ + ecs_id(id) = ecs_module_init(world, #id, &desc);\ + ecs_set_scope(world, ecs_id(id));\ + } + +#define ECS_MODULE(world, id)\ + ecs_entity_t ecs_id(id) = 0; ECS_MODULE_DEFINE(world, id)\ + (void)ecs_id(id) + +/** Wrapper around ecs_import(). + * This macro provides a convenient way to load a module with the world. It can + * be used like this: + * + * @code + * ECS_IMPORT(world, FlecsSystemsPhysics); + * @endcode + */ +#define ECS_IMPORT(world, id) ecs_import_c(world, id##Import, #id) + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_CPP +#ifdef FLECS_NO_CPP +#error "FLECS_NO_CPP failed: CPP is required by other addons" +#endif +/** + * @file addons/flecs_cpp.h + * @brief C++ utility functions + * + * This header contains utility functions that are accessible from both C and + * C++ code. These functions are not part of the public API and are not meant + * to be used directly by applications. + */ + +#ifdef FLECS_CPP + +#ifndef FLECS_CPP_H +#define FLECS_CPP_H + +#ifdef __cplusplus +extern "C" { +#endif + +// The functions in this file can be used from C or C++, but these macros are only relevant to C++. +#ifdef __cplusplus + +#if defined(__clang__) +#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" flecs::_::() [T = ") + sizeof(#name)) - 3u) +#define ECS_FUNC_NAME_BACK (sizeof("]") - 1u) +#define ECS_FUNC_NAME __PRETTY_FUNCTION__ +#elif defined(__GNUC__) +#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" flecs::_::() [with T = ") + sizeof(#name)) - 3u) +#define ECS_FUNC_NAME_BACK (sizeof("]") - 1u) +#define ECS_FUNC_NAME __PRETTY_FUNCTION__ +#elif defined(_WIN32) +#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" __cdecl flecs::_::<") + sizeof(#name)) - 3u) +#define ECS_FUNC_NAME_BACK (sizeof(">(void)") - 1u) +#define ECS_FUNC_NAME __FUNCSIG__ +#else +#error "implicit component registration not supported" +#endif + +#define ECS_FUNC_TYPE_LEN(type, name, str)\ + (flecs::string::length(str) - (ECS_FUNC_NAME_FRONT(type, name) + ECS_FUNC_NAME_BACK)) + +#endif + +FLECS_API +char* ecs_cpp_get_type_name( + char *type_name, + const char *func_name, + size_t len, + size_t front_len); + +FLECS_API +char* ecs_cpp_get_symbol_name( + char *symbol_name, + const char *type_name, + size_t len); + +FLECS_API +char* ecs_cpp_get_constant_name( + char *constant_name, + const char *func_name, + size_t len, + size_t back_len); + +FLECS_API +const char* ecs_cpp_trim_module( + ecs_world_t *world, + const char *type_name); + +FLECS_API +void ecs_cpp_component_validate( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + size_t size, + size_t alignment, + bool implicit_name); + +FLECS_API +ecs_entity_t ecs_cpp_component_register( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment, + bool implicit_name, + bool *existing_out); + +FLECS_API +ecs_entity_t ecs_cpp_component_register_explicit( + ecs_world_t *world, + ecs_entity_t s_id, + ecs_entity_t id, + const char *name, + const char *type_name, + const char *symbol, + size_t size, + size_t alignment, + bool is_component, + bool *existing_out); + +FLECS_API +void ecs_cpp_enum_init( + ecs_world_t *world, + ecs_entity_t id); + +FLECS_API +ecs_entity_t ecs_cpp_enum_constant_register( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t id, + const char *name, + int value); + +FLECS_API +int32_t ecs_cpp_reset_count_get(void); + +FLECS_API +int32_t ecs_cpp_reset_count_inc(void); + +#ifdef FLECS_META +FLECS_API +const ecs_member_t* ecs_cpp_last_member( + const ecs_world_t *world, + ecs_entity_t type); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // FLECS_CPP_H + +#endif // FLECS_CPP + + +#ifdef __cplusplus +/** + * @file addons/cpp/flecs.hpp + * @brief Flecs C++11 API. + */ + +#pragma once + +// STL includes +#include + +/** + * @defgroup cpp C++ API + * @{ + */ + +namespace flecs +{ + +struct world; +struct world_async_stage; +struct iter; +struct entity_view; +struct entity; +struct type; +struct table; +struct table_range; +struct untyped_component; + +template +struct component; + +namespace _ +{ +template +struct cpp_type; + +template +struct each_delegate; + +} // namespace _ +} // namespace flecs + +// Types imported from C API +/** + * @file addons/cpp/c_types.hpp + * @brief Aliases for types/constants from C API + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_globals API Types & Globals + * @ingroup cpp_core + * Types & constants bridged from C API. + * + * @{ + */ + +using world_t = ecs_world_t; +using world_info_t = ecs_world_info_t; +using query_group_info_t = ecs_query_group_info_t; +using id_t = ecs_id_t; +using entity_t = ecs_entity_t; +using type_t = ecs_type_t; +using table_t = ecs_table_t; +using filter_t = ecs_filter_t; +using observer_t = ecs_observer_t; +using query_t = ecs_query_t; +using rule_t = ecs_rule_t; +using ref_t = ecs_ref_t; +using iter_t = ecs_iter_t; +using type_info_t = ecs_type_info_t; +using type_hooks_t = ecs_type_hooks_t; +using flags32_t = ecs_flags32_t; + +enum inout_kind_t { + InOutDefault = EcsInOutDefault, + InOutNone = EcsInOutNone, + InOut = EcsInOut, + In = EcsIn, + Out = EcsOut +}; + +enum oper_kind_t { + And = EcsAnd, + Or = EcsOr, + Not = EcsNot, + Optional = EcsOptional, + AndFrom = EcsAndFrom, + OrFrom = EcsOrFrom, + NotFrom = EcsNotFrom +}; + +/** Id flags */ +static const flecs::entity_t Pair = ECS_PAIR; +static const flecs::entity_t Override = ECS_OVERRIDE; +static const flecs::entity_t Toggle = ECS_TOGGLE; + +//////////////////////////////////////////////////////////////////////////////// +//// Builtin components and tags +//////////////////////////////////////////////////////////////////////////////// + +/* Builtin components */ +using Component = EcsComponent; +using Identifier = EcsIdentifier; +using Poly = EcsPoly; +using Target = EcsTarget; + +/* Builtin tags */ +static const flecs::entity_t Query = EcsQuery; +static const flecs::entity_t Observer = EcsObserver; +static const flecs::entity_t Private = EcsPrivate; +static const flecs::entity_t Module = EcsModule; +static const flecs::entity_t Prefab = EcsPrefab; +static const flecs::entity_t Disabled = EcsDisabled; +static const flecs::entity_t Empty = EcsEmpty; +static const flecs::entity_t Monitor = EcsMonitor; +static const flecs::entity_t System = EcsSystem; +static const flecs::entity_t Pipeline = ecs_id(EcsPipeline); +static const flecs::entity_t Phase = EcsPhase; + +/* Builtin event tags */ +static const flecs::entity_t OnAdd = EcsOnAdd; +static const flecs::entity_t OnRemove = EcsOnRemove; +static const flecs::entity_t OnSet = EcsOnSet; +static const flecs::entity_t UnSet = EcsUnSet; +static const flecs::entity_t OnTableCreate = EcsOnTableCreate; +static const flecs::entity_t OnTableDelete = EcsOnTableDelete; + +/* Builtin term flags */ +static const uint32_t Self = EcsSelf; +static const uint32_t Up = EcsUp; +static const uint32_t Down = EcsDown; +static const uint32_t Cascade = EcsCascade; +static const uint32_t Desc = EcsDesc; +static const uint32_t Parent = EcsParent; +static const uint32_t IsVariable = EcsIsVariable; +static const uint32_t IsEntity = EcsIsEntity; +static const uint32_t Filter = EcsFilter; +static const uint32_t TraverseFlags = EcsTraverseFlags; + +/* Builtin entity ids */ +static const flecs::entity_t Flecs = EcsFlecs; +static const flecs::entity_t FlecsCore = EcsFlecsCore; +static const flecs::entity_t World = EcsWorld; + +/* Relationship properties */ +static const flecs::entity_t Wildcard = EcsWildcard; +static const flecs::entity_t Any = EcsAny; +static const flecs::entity_t This = EcsThis; +static const flecs::entity_t Transitive = EcsTransitive; +static const flecs::entity_t Reflexive = EcsReflexive; +static const flecs::entity_t Final = EcsFinal; +static const flecs::entity_t DontInherit = EcsDontInherit; +static const flecs::entity_t AlwaysOverride = EcsAlwaysOverride; +static const flecs::entity_t Tag = EcsTag; +static const flecs::entity_t Union = EcsUnion; +static const flecs::entity_t Exclusive = EcsExclusive; +static const flecs::entity_t Acyclic = EcsAcyclic; +static const flecs::entity_t Traversable = EcsTraversable; +static const flecs::entity_t Symmetric = EcsSymmetric; +static const flecs::entity_t With = EcsWith; +static const flecs::entity_t OneOf = EcsOneOf; + +/* Builtin relationships */ +static const flecs::entity_t IsA = EcsIsA; +static const flecs::entity_t ChildOf = EcsChildOf; +static const flecs::entity_t DependsOn = EcsDependsOn; +static const flecs::entity_t SlotOf = EcsSlotOf; + +/* Builtin identifiers */ +static const flecs::entity_t Name = EcsName; +static const flecs::entity_t Symbol = EcsSymbol; + +/* Cleanup policies */ +static const flecs::entity_t OnDelete = EcsOnDelete; +static const flecs::entity_t OnDeleteTarget = EcsOnDeleteTarget; +static const flecs::entity_t Remove = EcsRemove; +static const flecs::entity_t Delete = EcsDelete; +static const flecs::entity_t Panic = EcsPanic; + +/* Misc */ +static const flecs::entity_t Flatten = EcsFlatten; +static const flecs::entity_t DefaultChildComponent = EcsDefaultChildComponent; + +/* Builtin predicates for comparing entity ids in queries. Only supported by rules */ +static const flecs::entity_t PredEq = EcsPredEq; +static const flecs::entity_t PredMatch = EcsPredMatch; +static const flecs::entity_t PredLookup = EcsPredLookup; + +/* Builtin marker entities for query scopes */ +static const flecs::entity_t ScopeOpen = EcsScopeOpen; +static const flecs::entity_t ScopeClose = EcsScopeClose; + +/** @} */ + +} + + +// C++ utilities +/** + * @file addons/cpp/utils/utils.hpp + * @brief Flecs STL (FTL?) + * + * Flecs STL (FTL?) + * Minimalistic utilities that allow for STL like functionality without having + * to depend on the actual STL. + */ + +// Macros so that C++ new calls can allocate using ecs_os_api memory allocation functions +// Rationale: +// - Using macros here instead of a templated function bc clients might override ecs_os_malloc +// to contain extra debug info like source tracking location. Using a template function +// in that scenario would collapse all source location into said function vs. the +// actual call site +// - FLECS_PLACEMENT_NEW(): exists to remove any naked new calls/make it easy to identify any regressions +// by grepping for new/delete + +#define FLECS_PLACEMENT_NEW(_ptr, _type) ::new(flecs::_::placement_new_tag, _ptr) _type +#define FLECS_NEW(_type) FLECS_PLACEMENT_NEW(ecs_os_malloc(sizeof(_type)), _type) +#define FLECS_DELETE(_ptr) \ + do { \ + if (_ptr) { \ + flecs::_::destruct_obj(_ptr); \ + ecs_os_free(_ptr); \ + } \ + } while (false) + +/* Faster (compile time) alternatives to std::move / std::forward. From: + * https://www.foonathan.net/2020/09/move-forward/ + */ + +#define FLECS_MOV(...) \ + static_cast&&>(__VA_ARGS__) + +#define FLECS_FWD(...) \ + static_cast(__VA_ARGS__) + +namespace flecs +{ + +namespace _ +{ + +// Dummy Placement new tag to disambiguate from any other operator new overrides +struct placement_new_tag_t{}; +constexpr placement_new_tag_t placement_new_tag{}; +template inline void destruct_obj(Ty* _ptr) { _ptr->~Ty(); } +template inline void free_obj(Ty* _ptr) { + if (_ptr) { + destruct_obj(_ptr); + ecs_os_free(_ptr); + } +} + +} // namespace _ + +} // namespace flecs + +// Allows overriding flecs_static_assert, which is useful when testing +#ifndef flecs_static_assert +#define flecs_static_assert(cond, str) static_assert(cond, str) +#endif + +inline void* operator new(size_t, flecs::_::placement_new_tag_t, void* _ptr) noexcept { return _ptr; } +inline void operator delete(void*, flecs::_::placement_new_tag_t, void*) noexcept { } + +namespace flecs +{ + +// C++11/C++14 convenience template replacements + +template +using conditional_t = typename std::conditional::type; + +template +using decay_t = typename std::decay::type; + +template +using enable_if_t = typename std::enable_if::type; + +template +using remove_pointer_t = typename std::remove_pointer::type; + +template +using remove_reference_t = typename std::remove_reference::type; + +using std::is_base_of; +using std::is_empty; +using std::is_const; +using std::is_pointer; +using std::is_reference; +using std::is_volatile; +using std::is_same; +using std::is_enum; + +// Determine constness even if T is a pointer type +template +using is_const_p = is_const< remove_pointer_t >; + +// Apply cv modifiers from source type to destination type +// (from: https://stackoverflow.com/questions/52559336/add-const-to-type-if-template-arg-is-const) +template +using transcribe_const_t = conditional_t::value, Dst const, Dst>; + +template +using transcribe_volatile_t = conditional_t::value, Dst volatile, Dst>; + +template +using transcribe_cv_t = transcribe_const_t< Src, transcribe_volatile_t< Src, Dst> >; + +template +using transcribe_pointer_t = conditional_t::value, Dst*, Dst>; + +template +using transcribe_cvp_t = transcribe_cv_t< Src, transcribe_pointer_t< Src, Dst> >; + + +// More convenience templates. The if_*_t templates use int as default type +// instead of void. This enables writing code that's a bit less cluttered when +// the templates are used in a template declaration: +// +// enable_if_t* = nullptr +// vs: +// if_t = 0 + +template +using if_t = enable_if_t; + +template +using if_not_t = enable_if_t; + +namespace _ +{ + +// Utility to prevent static assert from immediately triggering +template +struct always_false { + static const bool value = false; +}; + +} // namespace _ + +} // namespace flecs + +#include +/** + * @file addons/cpp/utils/array.hpp + * @brief Array class. + * + * Array class. Simple std::array like utility that is mostly there to aid + * template code where template expansion would lead to an array with size 0. + */ + +namespace flecs { + +template +struct array_iterator +{ + explicit array_iterator(T* value, int index) { + m_value = value; + m_index = index; + } + + bool operator!=(array_iterator const& other) const + { + return m_index != other.m_index; + } + + T & operator*() const + { + return m_value[m_index]; + } + + array_iterator& operator++() + { + ++m_index; + return *this; + } + +private: + T* m_value; + int m_index; +}; + +template +struct array final { }; + +template +struct array > final { + array() {}; + + array(const T (&elems)[Size]) { + int i = 0; + for (auto it = this->begin(); it != this->end(); ++ it) { + *it = elems[i ++]; + } + } + + T& operator[](int index) { + return m_array[index]; + } + + T& operator[](size_t index) { + return m_array[index]; + } + + array_iterator begin() { + return array_iterator(m_array, 0); + } + + array_iterator end() { + return array_iterator(m_array, Size); + } + + size_t size() { + return Size; + } + + T* ptr() { + return m_array; + } + + template + void each(const Func& func) { + for (auto& elem : *this) { + func(elem); + } + } + +private: + T m_array[Size]; +}; + +template +array to_array(const T (&elems)[Size]) { + return array(elems); +} + +// Specialized class for zero-sized array +template +struct array> final { + array() {}; + array(const T* (&elems)) { (void)elems; } + T operator[](size_t index) { ecs_os_abort(); (void)index; return T(); } + array_iterator begin() { return array_iterator(nullptr, 0); } + array_iterator end() { return array_iterator(nullptr, 0); } + + size_t size() { + return 0; + } + + T* ptr() { + return NULL; + } +}; + +} + +/** + * @file addons/cpp/utils/string.hpp + * @brief String utility that doesn't implicitly allocate memory. + */ + +namespace flecs { + +struct string_view; + +// This removes dependencies on std::string (and therefore STL) and allows the +// API to return allocated strings without incurring additional allocations when +// wrapping in an std::string. +struct string { + explicit string() + : m_str(nullptr) + , m_const_str("") + , m_length(0) { } + + explicit string(char *str) + : m_str(str) + , m_const_str(str ? str : "") + , m_length(str ? ecs_os_strlen(str) : 0) { } + + ~string() { + // If flecs is included in a binary but is not used, it is possible that + // the OS API is not initialized. Calling ecs_os_free in that case could + // crash the application during exit. However, if a string has been set + // flecs has been used, and OS API should have been initialized. + if (m_str) { + ecs_os_free(m_str); + } + } + + string(string&& str) noexcept { + ecs_os_free(m_str); + m_str = str.m_str; + m_const_str = str.m_const_str; + m_length = str.m_length; + str.m_str = nullptr; + } + + operator const char*() const { + return m_const_str; + } + + string& operator=(string&& str) noexcept { + ecs_os_free(m_str); + m_str = str.m_str; + m_const_str = str.m_const_str; + m_length = str.m_length; + str.m_str = nullptr; + return *this; + } + + // Ban implicit copies/allocations + string& operator=(const string& str) = delete; + string(const string& str) = delete; + + bool operator==(const flecs::string& str) const { + if (str.m_const_str == m_const_str) { + return true; + } + + if (!m_const_str || !str.m_const_str) { + return false; + } + + if (str.m_length != m_length) { + return false; + } + + return ecs_os_strcmp(str, m_const_str) == 0; + } + + bool operator!=(const flecs::string& str) const { + return !(*this == str); + } + + bool operator==(const char *str) const { + if (m_const_str == str) { + return true; + } + + if (!m_const_str || !str) { + return false; + } + + return ecs_os_strcmp(str, m_const_str) == 0; + } + + bool operator!=(const char *str) const { + return !(*this == str); + } + + const char* c_str() const { + return m_const_str; + } + + std::size_t length() const { + return static_cast(m_length); + } + + template + static constexpr size_t length( char const (&)[N] ) { + return N - 1; + } + + std::size_t size() const { + return length(); + } + + void clear() { + ecs_os_free(m_str); + m_str = nullptr; + m_const_str = nullptr; + } + + bool contains(const char *substr) { + if (m_const_str) { + return strstr(m_const_str, substr) != nullptr; + } else { + return false; + } + } + +protected: + // Must be constructed through string_view. This allows for using the string + // class for both owned and non-owned strings, which can reduce allocations + // when code conditionally should store a literal or an owned string. + // Making this constructor private forces the code to explicitly create a + // string_view which emphasizes that the string won't be freed by the class. + string(const char *str) + : m_str(nullptr) + , m_const_str(str ? str : "") + , m_length(str ? ecs_os_strlen(str) : 0) { } + + char *m_str = nullptr; + const char *m_const_str; + ecs_size_t m_length; +}; + +// For consistency, the API returns a string_view where it could have returned +// a const char*, so an application won't have to think about whether to call +// c_str() or not. The string_view is a thin wrapper around a string that forces +// the API to indicate explicitly when a string is owned or not. +struct string_view : string { + explicit string_view(const char *str) + : string(str) { } +}; + +} + +/** + * @file addons/cpp/utils/enum.hpp + * @brief Compile time enum reflection utilities. + * + * Discover at compile time valid enumeration constants for an enumeration type + * and their names. This is used to automatically register enum constants. + */ + +#include + +#define FLECS_ENUM_MAX(T) _::to_constant::value +#define FLECS_ENUM_MAX_COUNT (FLECS_ENUM_MAX(int) + 1) + +#ifndef FLECS_CPP_ENUM_REFLECTION_SUPPORT +#if !defined(__clang__) && defined(__GNUC__) +#if __GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 5) +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1 +#else +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 0 +#endif +#else +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1 +#endif +#endif + +namespace flecs { + +/** Int to enum */ +namespace _ { +template +struct to_constant { +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + static constexpr E value = __builtin_bit_cast(E, Value); +#else + static constexpr E value = static_cast(Value); +#endif +}; + +template +constexpr E to_constant::value; +} + +/** Convenience type with enum reflection data */ +template +struct enum_data; + +template +static enum_data enum_type(flecs::world_t *world); + +template +struct enum_last { + static constexpr E value = FLECS_ENUM_MAX(E); +}; + +/* Utility macro to override enum_last trait */ +#define FLECS_ENUM_LAST(T, Last)\ + namespace flecs {\ + template<>\ + struct enum_last {\ + static constexpr T value = Last;\ + };\ + } + +namespace _ { + +#if INTPTR_MAX == INT64_MAX + #ifdef ECS_TARGET_MSVC + #if _MSC_VER >= 1930 + #define ECS_SIZE_T_STR "unsigned __int64" + #else + #define ECS_SIZE_T_STR "unsigned int" + #endif + #elif defined(__clang__) + #define ECS_SIZE_T_STR "size_t" + #else + #ifdef ECS_TARGET_WINDOWS + #define ECS_SIZE_T_STR "constexpr size_t; size_t = long long unsigned int" + #else + #define ECS_SIZE_T_STR "constexpr size_t; size_t = long unsigned int" + #endif + #endif +#else + #ifdef ECS_TARGET_MSVC + #if _MSC_VER >= 1930 + #define ECS_SIZE_T_STR "unsigned __int32" + #else + #define ECS_SIZE_T_STR "unsigned int" + #endif + #elif defined(__clang__) + #define ECS_SIZE_T_STR "size_t" + #else + #ifdef ECS_TARGET_WINDOWS + #define ECS_SIZE_T_STR "constexpr size_t; size_t = unsigned int" + #else + #define ECS_SIZE_T_STR "constexpr size_t; size_t = unsigned int" + #endif + #endif +#endif + +template +constexpr size_t enum_type_len() { + return ECS_FUNC_TYPE_LEN(, enum_type_len, ECS_FUNC_NAME) + - (sizeof(ECS_SIZE_T_STR) - 1u); +} + +/** Test if value is valid for enumeration. + * This function leverages that when a valid value is provided, + * __PRETTY_FUNCTION__ contains the enumeration name, whereas if a value is + * invalid, the string contains a number. */ +#if defined(ECS_TARGET_CLANG) +#if ECS_CLANG_VERSION < 13 +template +constexpr bool enum_constant_is_valid() { + return !( + (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', C = ' */] >= '0') && + (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', C = ' */] <= '9')); +} +#else +template +constexpr bool enum_constant_is_valid() { + return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', E C = ' */] != '('); +} +#endif +#elif defined(ECS_TARGET_GNU) +template +constexpr bool enum_constant_is_valid() { + return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(constexpr bool, enum_constant_is_valid) + + enum_type_len() + 8 /* ', E C = ' */] != '('); +} +#else +/* Use different trick on MSVC, since it uses hexadecimal representation for + * invalid enum constants. We can leverage that msvc inserts a C-style cast + * into the name, and the location of its first character ('(') is known. */ +template +constexpr bool enum_constant_is_valid() { + return ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 1] != '('; +} +#endif + +template +struct enum_is_valid { + static constexpr bool value = enum_constant_is_valid(); +}; + +/** Extract name of constant from string */ +template +static const char* enum_constant_to_name() { + static const size_t len = ECS_FUNC_TYPE_LEN(const char*, enum_constant_to_name, ECS_FUNC_NAME); + static char result[len + 1] = {}; + return ecs_cpp_get_constant_name( + result, ECS_FUNC_NAME, string::length(ECS_FUNC_NAME), + ECS_FUNC_NAME_BACK); +} + +/** Enumeration constant data */ +struct enum_constant_data { + flecs::entity_t id; + int next; +}; + +/** Enumeration type data */ +struct enum_data_impl { + flecs::entity_t id; + int min; + int max; + enum_constant_data constants[FLECS_ENUM_MAX_COUNT]; +}; + +/** Class that scans an enum for constants, extracts names & creates entities */ +template +struct enum_type { + static enum_data_impl data; + + static enum_type& get() { + static _::enum_type instance; + return instance; + } + + flecs::entity_t entity(E value) const { + return data.constants[static_cast(value)].id; + } + + void init(flecs::world_t *world, flecs::entity_t id) { +#if !FLECS_CPP_ENUM_REFLECTION_SUPPORT + ecs_abort(ECS_UNSUPPORTED, "enum reflection requires gcc 7.5 or higher") +#endif + + ecs_log_push(); + ecs_cpp_enum_init(world, id); + data.id = id; + data.min = FLECS_ENUM_MAX(int); + init< enum_last::value >(world); + ecs_log_pop(); + } + +private: + template + static constexpr int to_int() { + return static_cast(Value); + } + + template + static constexpr E from_int() { + return to_constant::value; + } + + template + static constexpr int is_not_0() { + return static_cast(Value != from_int<0>()); + } + + template () > = 0> + static void init_constant(flecs::world_t*) { } + + template () > = 0> + static void init_constant(flecs::world_t *world) { + int v = to_int(); + const char *name = enum_constant_to_name(); + data.constants[v].next = data.min; + data.min = v; + if (!data.max) { + data.max = v; + } + + data.constants[v].id = ecs_cpp_enum_constant_register( + world, data.id, data.constants[v].id, name, v); + } + + template + static void init(flecs::world_t *world) { + init_constant(world); + if (is_not_0()) { + init() - is_not_0()>()>(world); + } + } +}; + +template +enum_data_impl enum_type::data; + +template ::value > = 0> +inline static void init_enum(flecs::world_t *world, flecs::entity_t id) { + _::enum_type::get().init(world, id); +} + +template ::value > = 0> +inline static void init_enum(flecs::world_t*, flecs::entity_t) { } + +} // namespace _ + +/** Enumeration type data wrapper with world pointer */ +template +struct enum_data { + enum_data(flecs::world_t *world, _::enum_data_impl& impl) + : world_(world) + , impl_(impl) { } + + bool is_valid(int value) { + return impl_.constants[value].id != 0; + } + + int first() const { + return impl_.min; + } + + int last() const { + return impl_.max; + } + + int next(int cur) const { + return impl_.constants[cur].next; + } + + flecs::entity entity() const; + flecs::entity entity(int value) const; + flecs::entity entity(E value) const; + + flecs::world_t *world_; + _::enum_data_impl& impl_; +}; + +/** Convenience function for getting enum reflection data */ +template +enum_data enum_type(flecs::world_t *world) { + _::cpp_type::id(world); // Ensure enum is registered + auto& ref = _::enum_type::get(); + return enum_data(world, ref.data); +} + +} // namespace flecs + +/** + * @file addons/cpp/utils/stringstream.hpp + * @brief Wrapper around ecs_strbuf_t that provides a simple stringstream like API. + */ + +namespace flecs { + +struct stringstream { + explicit stringstream() + : m_buf({}) { } + + ~stringstream() { + ecs_strbuf_reset(&m_buf); + } + + stringstream(stringstream&& str) noexcept { + ecs_strbuf_reset(&m_buf); + m_buf = str.m_buf; + str.m_buf = {}; + } + + stringstream& operator=(stringstream&& str) noexcept { + ecs_strbuf_reset(&m_buf); + m_buf = str.m_buf; + str.m_buf = {}; + return *this; + } + + // Ban implicit copies/allocations + stringstream& operator=(const stringstream& str) = delete; + stringstream(const stringstream& str) = delete; + + stringstream& operator<<(const char* str) { + ecs_strbuf_appendstr(&m_buf, str); + return *this; + } + + flecs::string str() { + return flecs::string(ecs_strbuf_get(&m_buf)); + } + +private: + ecs_strbuf_t m_buf; +}; + +} + +/** + * @file addons/cpp/utils/function_traits.hpp + * @brief Compile time utilities to inspect properties of functions. + * + * Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work + */ + +namespace flecs { +namespace _ { + +template +struct arg_list { }; + +// Base type that contains the traits +template +struct function_traits_defs +{ + static constexpr bool is_callable = true; + static constexpr size_t arity = sizeof...(Args); + using return_type = ReturnType; + using args = arg_list; +}; + +// Primary template for function_traits_impl +template +struct function_traits_impl { + static constexpr bool is_callable = false; +}; + +// Template specializations for the different kinds of function types (whew) +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +// Primary template for function_traits_no_cv. If T is not a function, the +// compiler will attempt to instantiate this template and fail, because its base +// is undefined. +template +struct function_traits_no_cv + : function_traits_impl {}; + +// Specialized template for function types +template +struct function_traits_no_cv + : function_traits_impl {}; + +// Front facing template that decays T before ripping it apart. +template +struct function_traits + : function_traits_no_cv< decay_t > {}; + +} // _ + + +template +struct is_callable { + static constexpr bool value = _::function_traits::is_callable; +}; + +template +struct arity { + static constexpr int value = _::function_traits::arity; +}; + +template +using return_type_t = typename _::function_traits::return_type; + +template +using arg_list_t = typename _::function_traits::args; + +// First arg +template +struct first_arg_impl; + +template +struct first_arg_impl > { + using type = T; +}; + +template +struct first_arg { + using type = typename first_arg_impl>::type; +}; + +template +using first_arg_t = typename first_arg::type; + +// Last arg +template +struct second_arg_impl; + +template +struct second_arg_impl > { + using type = T; +}; + +template +struct second_arg { + using type = typename second_arg_impl>::type; +}; + +template +using second_arg_t = typename second_arg::type; + +} // flecs + + + +// Mixin forward declarations +/** + * @file addons/cpp/mixins/id/decl.hpp + * @brief Id class. + */ + +#pragma once + +namespace flecs { + +struct id; +struct entity; + +/** + * @defgroup cpp_ids Ids + * @ingroup cpp_core + * Class for working with entity, component, tag and pair ids. + * + * @{ + */ + +/** Class that wraps around a flecs::id_t. + * A flecs id is an identifier that can be added to entities. Ids can be: + * - entities (including components, tags) + * - pair ids + * - entities with id flags set (like flecs::Override, flecs::Toggle) + */ +struct id { + id() + : m_world(nullptr) + , m_id(0) { } + + explicit id(flecs::id_t value) + : m_world(nullptr) + , m_id(value) { } + + explicit id(flecs::world_t *world, flecs::id_t value = 0) + : m_world(world) + , m_id(value) { } + + explicit id(flecs::world_t *world, flecs::id_t first, flecs::id_t second) + : m_world(world) + , m_id(ecs_pair(first, second)) { } + + explicit id(flecs::id_t first, flecs::id_t second) + : m_world(nullptr) + , m_id(ecs_pair(first, second)) { } + + explicit id(const flecs::id& first, const flecs::id& second) + : m_world(first.m_world) + , m_id(ecs_pair(first.m_id, second.m_id)) { } + + /** Test if id is pair (has first, second) */ + bool is_pair() const { + return (m_id & ECS_ID_FLAGS_MASK) == flecs::Pair; + } + + /** Test if id is a wildcard */ + bool is_wildcard() const { + return ecs_id_is_wildcard(m_id); + } + + /** Test if id is entity */ + bool is_entity() const { + return !(m_id & ECS_ID_FLAGS_MASK); + } + + /** Return id as entity (only allowed when id is valid entity) */ + flecs::entity entity() const; + + /** Return id with role added */ + flecs::entity add_flags(flecs::id_t flags) const; + + /** Return id with role removed */ + flecs::entity remove_flags(flecs::id_t flags) const; + + /** Return id without role */ + flecs::entity remove_flags() const; + + /** Return id without role */ + flecs::entity remove_generation() const; + + /** Return component type of id */ + flecs::entity type_id() const; + + /** Test if id has specified role */ + bool has_flags(flecs::id_t flags) const { + return ((m_id & flags) == flags); + } + + /** Test if id has any role */ + bool has_flags() const { + return (m_id & ECS_ID_FLAGS_MASK) != 0; + } + + /** Return id flags set on id */ + flecs::entity flags() const; + + /** Test if id has specified first */ + bool has_relation(flecs::id_t first) const { + if (!is_pair()) { + return false; + } + return ECS_PAIR_FIRST(m_id) == first; + } + + /** Get first element from a pair. + * If the id is not a pair, this operation will fail. When the id has a + * world, the operation will ensure that the returned id has the correct + * generation count. */ + flecs::entity first() const; + + /** Get second element from a pair. + * If the id is not a pair, this operation will fail. When the id has a + * world, the operation will ensure that the returned id has the correct + * generation count. */ + flecs::entity second() const; + + /* Convert id to string */ + flecs::string str() const { + return flecs::string(ecs_id_str(m_world, m_id)); + } + + /** Convert role of id to string. */ + flecs::string flags_str() const { + return flecs::string_view( ecs_id_flag_str(m_id & ECS_ID_FLAGS_MASK)); + } + + /** Return flecs::id_t value */ + flecs::id_t raw_id() const { + return m_id; + } + + operator flecs::id_t() const { + return m_id; + } + + flecs::world world() const; + +protected: + /* World is optional, but guarantees that entity identifiers extracted from + * the id are valid */ + flecs::world_t *m_world; + flecs::id_t m_id; +}; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/term/decl.hpp + * @brief Term declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @ingroup cpp_core_filters + * + * @{ + */ + +struct term; +struct term_builder; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/filter/decl.hpp + * @brief Filter declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_core_filters Filters + * @ingroup cpp_core + * Filters are cheaper to create, but slower to iterate than flecs::query. + * + * @{ + */ + +struct filter_base; + +template +struct filter; + +template +struct filter_builder; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/event/decl.hpp + * @brief Event declarations. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/event/builder.hpp + * @brief Event builder. + */ + +#pragma once + +#define ECS_EVENT_DESC_ID_COUNT_MAX (8) + +namespace flecs { + +/** + * @ingroup cpp_addons_event + * @{ + */ + +/** Event builder interface */ +template +struct event_builder_base { + event_builder_base(flecs::world_t *world, flecs::entity_t event) + : m_world(world) + , m_desc{} + , m_ids{} + , m_ids_array{} + { + m_desc.event = event; + } + + /** Add component to emit for */ + template + Base& id() { + m_ids.array = m_ids_array; + m_ids.array[m_ids.count] = _::cpp_type().id(m_world); + m_ids.count ++; + return *this; + } + + /** + * Add pair to emit for + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template + Base& id() { + return id( + ecs_pair(_::cpp_type::id(this->m_world), + _::cpp_type::id(this->m_world))); + } + + /** + * Add pair to emit for + * @tparam First The first element of the pair. + * @param second The second element of the pair id. + */ + template + Base& id(entity_t second) { + return id(ecs_pair(_::cpp_type::id(this->m_world), second)); + } + + /** + * Add pair to emit for + * @param first The first element of the pair type. + * @param second The second element of the pair id. + */ + Base& id(entity_t first, entity_t second) { + return id(ecs_pair(first, second)); + } + + /** Add (component) id to emit for */ + Base& id(flecs::id_t id) { + m_ids.array = m_ids_array; + m_ids.array[m_ids.count] = id; + m_ids.count ++; + return *this; + } + + /** Set entity for which to emit event */ + Base& entity(flecs::entity_t e) { + m_desc.entity = e; + return *this; + } + + /* Set table for which to emit event */ + Base& table(flecs::table_t *t, int32_t offset = 0, int32_t count = 0) { + m_desc.table = t; + m_desc.offset = offset; + m_desc.count = count; + return *this; + } + + /* Set event data */ + Base& ctx(const E* ptr) { + m_desc.const_param = ptr; + return *this; + } + + /* Set event data */ + Base& ctx(E* ptr) { + m_desc.param = ptr; + return *this; + } + + void emit() { + m_ids.array = m_ids_array; + m_desc.ids = &m_ids; + m_desc.observable = const_cast(ecs_get_world(m_world)); + ecs_emit(m_world, &m_desc); + } + + void enqueue() { + m_ids.array = m_ids_array; + m_desc.ids = &m_ids; + m_desc.observable = const_cast(ecs_get_world(m_world)); + ecs_enqueue(m_world, &m_desc); + } + +protected: + flecs::world_t *m_world; + ecs_event_desc_t m_desc; + flecs::type_t m_ids; + flecs::id_t m_ids_array[ECS_EVENT_DESC_ID_COUNT_MAX]; + +private: + operator Base&() { + return *static_cast(this); + } +}; + +struct event_builder : event_builder_base { + using event_builder_base::event_builder_base; +}; + +template +struct event_builder_typed : event_builder_base, E> { +private: + using Class = event_builder_typed; + +public: + using event_builder_base::event_builder_base; + + /* Set event data */ + Class& ctx(const E& ptr) { + this->m_desc.const_param = &ptr; + return *this; + } + + /* Set event data */ + Class& ctx(E&& ptr) { + this->m_desc.param = &ptr; + return *this; + } +}; + +/** @} */ + +} + + +namespace flecs { +namespace _ { + +// Utility to derive event type from function +template +struct event_from_func; + +// Specialization for observer callbacks with a single argument +template +struct event_from_func::value == 1>> { + using type = decay_t>; +}; + +// Specialization for observer callbacks with an initial entity src argument +template +struct event_from_func::value == 2>> { + using type = decay_t>; +}; + +template +using event_from_func_t = typename event_from_func::type; + +} +} + +/** + * @file addons/cpp/mixins/query/decl.hpp + * @brief Query declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_core_queries Queries + * @ingroup cpp_core + * Cached query implementation. Fast to iterate, but slower to create than flecs::filter. + * + * @{ + */ + +struct query_base; + +template +struct query; + +template +struct query_builder; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/observer/decl.hpp + * @brief Observer declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_observers Observers + * @ingroup cpp_core + * Observers let applications register callbacks for ECS events. + * + * @{ + */ + +struct observer; + +template +struct observer_builder; + +/** @} */ + +} + +#ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/decl.hpp + * @brief System module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_systems Systems + * @ingroup cpp_addons + * Systems are a query + function that can be ran manually or by a pipeline. + * + * @{ + */ + +using TickSource = EcsTickSource; + +struct system; + +template +struct system_builder; + +namespace _ { + +void system_init(flecs::world& world); + +/** @} */ + +} // namespace _ +} // namespace flecs + +#endif +#ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/decl.hpp + * @brief Pipeline module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_pipelines Pipelines + * @ingroup cpp_addons + * Pipelines order and schedule systems for execution. + * + * @{ + */ + +template +struct pipeline; + +template +struct pipeline_builder; + +/* Builtin pipeline tags */ +static const flecs::entity_t OnStart = EcsOnStart; +static const flecs::entity_t PreFrame = EcsPreFrame; +static const flecs::entity_t OnLoad = EcsOnLoad; +static const flecs::entity_t PostLoad = EcsPostLoad; +static const flecs::entity_t PreUpdate = EcsPreUpdate; +static const flecs::entity_t OnUpdate = EcsOnUpdate; +static const flecs::entity_t OnValidate = EcsOnValidate; +static const flecs::entity_t PostUpdate = EcsPostUpdate; +static const flecs::entity_t PreStore = EcsPreStore; +static const flecs::entity_t OnStore = EcsOnStore; +static const flecs::entity_t PostFrame = EcsPostFrame; + +/** @} */ + +} + +#endif +#ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/decl.hpp + * @brief Timer module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_timer Timer + * @ingroup cpp_addons + * Run systems at a time interval. + * + * @{ + */ + +using Timer = EcsTimer; +using RateFilter = EcsRateFilter; + +struct timer; + +/** @} */ + +namespace _ { + +void timer_init(flecs::world& world); + +} // namespace _ +} // namespace flecs + +#endif +#ifdef FLECS_SNAPSHOT +/** + * @file addons/cpp/mixins/snapshot/decl.hpp + * @brief Snapshot module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_snapshots Snapshots + * @ingroup cpp_addons + * Save & restore world. + * + * @{ + */ + +using snapshot_t = ecs_snapshot_t; + +struct snapshot; + +/** @} */ + +} + +#endif +#ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/decl.hpp + * @brief Doc mixin declarations. + */ + +#pragma once + +namespace flecs { +namespace doc { + +/** + * @defgroup cpp_addons_doc Doc + * @ingroup cpp_addons + * Utilities for documenting entities, components and systems. + * + * @{ + */ + +/** flecs.doc.Description component */ +using Description = EcsDocDescription; + +/** flecs.doc.Brief component */ +static const flecs::entity_t Brief = EcsDocBrief; + +/** flecs.doc.Detail component */ +static const flecs::entity_t Detail = EcsDocDetail; + +/** flecs.doc.Link component */ +static const flecs::entity_t Link = EcsDocLink; + +/** flecs.doc.Color component */ +static const flecs::entity_t Color = EcsDocColor; + +/** @private */ +namespace _ { +/** @private */ +void init(flecs::world& world); +} + +/** @} */ + +} +} + +#endif +#ifdef FLECS_REST +/** + * @file addons/cpp/mixins/rest/decl.hpp + * @brief Rest module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_rest Rest + * @ingroup cpp_addons + * REST API for querying and mutating entities. + * + * @{ + */ + +using Rest = EcsRest; + +namespace rest { + +namespace _ { + +void init(flecs::world& world); + +} +} + +/** @} */ + +} + +#endif +#ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/decl.hpp + * @brief Rule declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_rules Rules + * @ingroup cpp_addons + * Rules are an advanced query engine for matching against entity graphs. + * + * @{ + */ + +struct rule_base; + +template +struct rule; + +template +struct rule_builder; + +/** @} */ + +} + +#endif +#ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/decl.hpp + * @brief Meta declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_meta Meta + * @ingroup cpp_addons + * Flecs reflection framework. + * + * @{ + */ + +/* Primitive type aliases */ +using bool_t = ecs_bool_t; +using char_t = ecs_char_t; +using u8_t = ecs_u8_t; +using u16_t = ecs_u16_t; +using u32_t = ecs_u32_t; +using u64_t = ecs_u64_t; +using uptr_t = ecs_uptr_t; +using i8_t = ecs_i8_t; +using i16_t = ecs_i16_t; +using i32_t = ecs_i32_t; +using i64_t = ecs_i64_t; +using iptr_t = ecs_iptr_t; +using f32_t = ecs_f32_t; +using f64_t = ecs_f64_t; + +/* Embedded type aliases */ +using member_t = ecs_member_t; +using enum_constant_t = ecs_enum_constant_t; +using bitmask_constant_t = ecs_bitmask_constant_t; + +/* Components */ +using MetaType = EcsMetaType; +using MetaTypeSerialized = EcsMetaTypeSerialized; +using Primitive = EcsPrimitive; +using Enum = EcsEnum; +using Bitmask = EcsBitmask; +using Member = EcsMember; +using MemberRanges = EcsMemberRanges; +using Struct = EcsStruct; +using Array = EcsArray; +using Vector = EcsVector; +using Unit = EcsUnit; + +/** Base type for bitmasks */ +struct bitmask { + uint32_t value; +}; + +/* Handles to builtin reflection types */ +static const flecs::entity_t Bool = ecs_id(ecs_bool_t); +static const flecs::entity_t Char = ecs_id(ecs_char_t); +static const flecs::entity_t Byte = ecs_id(ecs_byte_t); +static const flecs::entity_t U8 = ecs_id(ecs_u8_t); +static const flecs::entity_t U16 = ecs_id(ecs_u16_t); +static const flecs::entity_t U32 = ecs_id(ecs_u32_t); +static const flecs::entity_t U64 = ecs_id(ecs_u64_t); +static const flecs::entity_t Uptr = ecs_id(ecs_uptr_t); +static const flecs::entity_t I8 = ecs_id(ecs_i8_t); +static const flecs::entity_t I16 = ecs_id(ecs_i16_t); +static const flecs::entity_t I32 = ecs_id(ecs_i32_t); +static const flecs::entity_t I64 = ecs_id(ecs_i64_t); +static const flecs::entity_t Iptr = ecs_id(ecs_iptr_t); +static const flecs::entity_t F32 = ecs_id(ecs_f32_t); +static const flecs::entity_t F64 = ecs_id(ecs_f64_t); +static const flecs::entity_t String = ecs_id(ecs_string_t); +static const flecs::entity_t Entity = ecs_id(ecs_entity_t); +static const flecs::entity_t Constant = EcsConstant; +static const flecs::entity_t Quantity = EcsQuantity; + +namespace meta { + +/* Type kinds supported by reflection system */ +using type_kind_t = ecs_type_kind_t; +static const type_kind_t PrimitiveType = EcsPrimitiveType; +static const type_kind_t BitmaskType = EcsBitmaskType; +static const type_kind_t EnumType = EcsEnumType; +static const type_kind_t StructType = EcsStructType; +static const type_kind_t ArrayType = EcsArrayType; +static const type_kind_t VectorType = EcsVectorType; +static const type_kind_t CustomType = EcsOpaqueType; +static const type_kind_t TypeKindLast = EcsTypeKindLast; + +/* Primitive type kinds supported by reflection system */ +using primitive_kind_t = ecs_primitive_kind_t; +static const primitive_kind_t Bool = EcsBool; +static const primitive_kind_t Char = EcsChar; +static const primitive_kind_t Byte = EcsByte; +static const primitive_kind_t U8 = EcsU8; +static const primitive_kind_t U16 = EcsU16; +static const primitive_kind_t U32 = EcsU32; +static const primitive_kind_t U64 = EcsU64; +static const primitive_kind_t I8 = EcsI8; +static const primitive_kind_t I16 = EcsI16; +static const primitive_kind_t I32 = EcsI32; +static const primitive_kind_t I64 = EcsI64; +static const primitive_kind_t F32 = EcsF32; +static const primitive_kind_t F64 = EcsF64; +static const primitive_kind_t UPtr = EcsUPtr; +static const primitive_kind_t IPtr = EcsIPtr; +static const primitive_kind_t String = EcsString; +static const primitive_kind_t Entity = EcsEntity; +static const primitive_kind_t PrimitiveKindLast = EcsPrimitiveKindLast; + +/** @} */ + +namespace _ { + +void init(flecs::world& world); + +} // namespace _ +} // namespace meta +} // namespace flecs + +/** + * @file addons/cpp/mixins/meta/opaque.hpp + * @brief Helpers for opaque type registration. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_meta Meta + * @ingroup cpp_addons + * Flecs reflection framework. + * + * @{ + */ + +/** Class for reading/writing dynamic values. + * + * @ingroup cpp_addons_meta + */ +struct cursor { + cursor(flecs::world_t *world, flecs::entity_t type_id, void *ptr) { + m_cursor = ecs_meta_cursor(world, type_id, ptr); + } + + /** Push value scope (such as a nested struct) */ + int push() { + return ecs_meta_push(&m_cursor); + } + + /** Pop value scope */ + int pop() { + return ecs_meta_pop(&m_cursor); + } + + /** Move to next member/element */ + int next() { + return ecs_meta_next(&m_cursor); + } + + /** Move to member by name */ + int member(const char *name) { + return ecs_meta_member(&m_cursor, name); + } + + /** Move to element by index */ + int elem(int32_t elem) { + return ecs_meta_elem(&m_cursor, elem); + } + + /** Test if current scope is a collection type */ + bool is_collection() { + return ecs_meta_is_collection(&m_cursor); + } + + /** Get member name */ + flecs::string_view get_member() const { + return flecs::string_view(ecs_meta_get_member(&m_cursor)); + } + + /** Get type of value */ + flecs::entity get_type() const; + + /** Get unit of value */ + flecs::entity get_unit() const; + + /** Get untyped pointer to value */ + void* get_ptr() { + return ecs_meta_get_ptr(&m_cursor); + } + + /** Set boolean value */ + int set_bool(bool value) { + return ecs_meta_set_bool(&m_cursor, value); + } + + /** Set char value */ + int set_char(char value) { + return ecs_meta_set_char(&m_cursor, value); + } + + /** Set signed int value */ + int set_int(int64_t value) { + return ecs_meta_set_int(&m_cursor, value); + } + + /** Set unsigned int value */ + int set_uint(uint64_t value) { + return ecs_meta_set_uint(&m_cursor, value); + } + + /** Set float value */ + int set_float(double value) { + return ecs_meta_set_float(&m_cursor, value); + } + + /** Set string value */ + int set_string(const char *value) { + return ecs_meta_set_string(&m_cursor, value); + } + + /** Set string literal value */ + int set_string_literal(const char *value) { + return ecs_meta_set_string_literal(&m_cursor, value); + } + + /** Set entity value */ + int set_entity(flecs::entity_t value) { + return ecs_meta_set_entity(&m_cursor, value); + } + + /** Set (component) id value */ + int set_id(flecs::id_t value) { + return ecs_meta_set_id(&m_cursor, value); + } + + /** Set null value */ + int set_null() { + return ecs_meta_set_null(&m_cursor); + } + + /** Get boolean value */ + bool get_bool() const { + return ecs_meta_get_bool(&m_cursor); + } + + /** Get char value */ + char get_char() const { + return ecs_meta_get_char(&m_cursor); + } + + /** Get signed int value */ + int64_t get_int() const { + return ecs_meta_get_int(&m_cursor); + } + + /** Get unsigned int value */ + uint64_t get_uint() const { + return ecs_meta_get_uint(&m_cursor); + } + + /** Get float value */ + double get_float() const { + return ecs_meta_get_float(&m_cursor); + } + + /** Get string value */ + const char *get_string() const { + return ecs_meta_get_string(&m_cursor); + } + + /** Get entity value */ + flecs::entity get_entity() const; + + /** Cursor object */ + ecs_meta_cursor_t m_cursor; +}; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/meta/opaque.hpp + * @brief Helpers for opaque type registration. + */ + +#pragma once + +#include + +namespace flecs { + +/** + * @defgroup cpp_addons_meta Meta + * @ingroup cpp_addons + * Flecs reflection framework. + * + * @{ + */ + +/** Serializer object, used for serializing opaque types */ +using serializer = ecs_serializer_t; + +/** Serializer function, used to serialize opaque types */ +using serialize_t = ecs_meta_serialize_t; + +/** Type safe variant of serializer function */ +template +using serialize = int(*)(const serializer *, const T*); + +/** Type safe interface for opaque types */ +template +struct opaque { + opaque(flecs::world_t *w = nullptr) : world(w) { + if (world) { + desc.entity = _::cpp_type::id(world); + } + } + + /** Type that describes the type kind/structure of the opaque type */ + opaque& as_type(flecs::id_t func) { + this->desc.type.as_type = func; + return *this; + } + + /** Serialize function */ + opaque& serialize(flecs::serialize func) { + this->desc.type.serialize = + reinterpret_castdesc.type.serialize)>(func); + return *this; + } + + /** Assign bool value */ + opaque& assign_bool(void (*func)(T *dst, bool value)) { + this->desc.type.assign_bool = + reinterpret_castdesc.type.assign_bool)>(func); + return *this; + } + + /** Assign char value */ + opaque& assign_char(void (*func)(T *dst, char value)) { + this->desc.type.assign_char = + reinterpret_castdesc.type.assign_char)>(func); + return *this; + } + + /** Assign int value */ + opaque& assign_int(void (*func)(T *dst, int64_t value)) { + this->desc.type.assign_int = + reinterpret_castdesc.type.assign_int)>(func); + return *this; + } + + /** Assign unsigned int value */ + opaque& assign_uint(void (*func)(T *dst, uint64_t value)) { + this->desc.type.assign_uint = + reinterpret_castdesc.type.assign_uint)>(func); + return *this; + } + + /** Assign float value */ + opaque& assign_float(void (*func)(T *dst, double value)) { + this->desc.type.assign_float = + reinterpret_castdesc.type.assign_float)>(func); + return *this; + } + + /** Assign string value */ + opaque& assign_string(void (*func)(T *dst, const char *value)) { + this->desc.type.assign_string = + reinterpret_castdesc.type.assign_string)>(func); + return *this; + } + + /** Assign entity value */ + opaque& assign_entity( + void (*func)(T *dst, ecs_world_t *world, ecs_entity_t entity)) + { + this->desc.type.assign_entity = + reinterpret_castdesc.type.assign_entity)>(func); + return *this; + } + + /** Assign (component) id value */ + opaque& assign_id( + void (*func)(T *dst, ecs_world_t *world, ecs_id_t id)) + { + this->desc.type.assign_id = + reinterpret_castdesc.type.assign_id)>(func); + return *this; + } + + /** Assign null value */ + opaque& assign_null(void (*func)(T *dst)) { + this->desc.type.assign_null = + reinterpret_castdesc.type.assign_null)>(func); + return *this; + } + + /** Clear collection elements */ + opaque& clear(void (*func)(T *dst)) { + this->desc.type.clear = + reinterpret_castdesc.type.clear)>(func); + return *this; + } + + /** Ensure & get collection element */ + opaque& ensure_element(ElemType* (*func)(T *dst, size_t elem)) { + this->desc.type.ensure_element = + reinterpret_castdesc.type.ensure_element)>(func); + return *this; + } + + /** Ensure & get element */ + opaque& ensure_member(void* (*func)(T *dst, const char *member)) { + this->desc.type.ensure_member = + reinterpret_castdesc.type.ensure_member)>(func); + return *this; + } + + /** Return number of elements */ + opaque& count(size_t (*func)(const T *dst)) { + this->desc.type.count = + reinterpret_castdesc.type.count)>(func); + return *this; + } + + /** Resize to number of elements */ + opaque& resize(void (*func)(T *dst, size_t count)) { + this->desc.type.resize = + reinterpret_castdesc.type.resize)>(func); + return *this; + } + + ~opaque() { + if (world) { + ecs_opaque_init(world, &desc); + } + } + + /** Opaque type descriptor */ + flecs::world_t *world = nullptr; + ecs_opaque_desc_t desc = {}; +}; + +/** @} */ + +} + + +#endif +#ifdef FLECS_UNITS +/** + * @file addons/cpp/mixins/units/decl.hpp + * @brief Units module declarations. + */ + +#pragma once + +namespace flecs { +struct units { + +/** + * @defgroup cpp_addons_units Units + * @ingroup cpp_addons + * Common unit annotations for reflection framework. + * + * @{ + */ + +struct Prefixes { }; + +/** + * @defgroup cpp_addons_units_prefixes Prefixes + * @ingroup cpp_addons_units + * Prefixes to indicate unit count (e.g. Kilo, Mega) + * + * @{ + */ + +struct Yocto { }; +struct Zepto { }; +struct Atto { }; +struct Femto { }; +struct Pico { }; +struct Nano { }; +struct Micro { }; +struct Milli { }; +struct Centi { }; +struct Deci { }; +struct Deca { }; +struct Hecto { }; +struct Kilo { }; +struct Mega { }; +struct Giga { }; +struct Tera { }; +struct Peta { }; +struct Exa { }; +struct Zetta { }; +struct Yotta { }; +struct Kibi { }; +struct Mebi { }; +struct Gibi { }; +struct Tebi { }; +struct Pebi { }; +struct Exbi { }; +struct Zebi { }; +struct Yobi { }; + +/** @} */ + +/** + * @defgroup cpp_addons_units_quantities Quantities + * @ingroup cpp_addons_units + * Quantities that group units (e.g. Length) + * + * @{ + */ + +struct Duration { }; +struct Time { }; +struct Mass { }; +struct ElectricCurrent { }; +struct LuminousIntensity { }; +struct Force { }; +struct Amount { }; +struct Length { }; +struct Pressure { }; +struct Speed { }; +struct Temperature { }; +struct Data { }; +struct DataRate { }; +struct Angle { }; +struct Frequency { }; +struct Uri { }; + +/** @} */ + +struct duration { +/** + * @defgroup cpp_addons_units_duration Duration + * @ingroup cpp_addons_units + * @{ + */ + +struct PicoSeconds { }; +struct NanoSeconds { }; +struct MicroSeconds { }; +struct MilliSeconds { }; +struct Seconds { }; +struct Minutes { }; +struct Hours { }; +struct Days { }; + +/** @} */ +}; + +struct angle { +/** + * @defgroup cpp_addons_units_angle Angle + * @ingroup cpp_addons_units + * @{ + */ + +struct Radians { }; +struct Degrees { }; + +/** @} */ +}; + + +struct time { +/** + * @defgroup cpp_addons_units_time Time + * @ingroup cpp_addons_units + * @{ + */ + +struct Date { }; + +/** @} */ +}; + + +struct mass { +/** + * @defgroup cpp_addons_units_mass Mass + * @ingroup cpp_addons_units + * @{ + */ + +struct Grams { }; +struct KiloGrams { }; + +/** @} */ +}; + + +struct electric_current { +/** + * @defgroup cpp_addons_units_electric_current Electric Current + * @ingroup cpp_addons_units + * @{ + */ + +struct Ampere { }; + +/** @} */ +}; + + +struct amount { +/** + * @defgroup cpp_addons_units_amount Amount + * @ingroup cpp_addons_units + * @{ + */ + +struct Mole { }; + +/** @} */ +}; + + +struct luminous_intensity { +/** + * @defgroup cpp_addons_units_luminous_intensity Luminous Intensity + * @ingroup cpp_addons_units + * @{ + */ + +struct Candela { }; + +/** @} */ +}; + + +struct force { +/** + * @defgroup cpp_addons_units_force Force + * @ingroup cpp_addons_units + * @{ + */ + +struct Newton { }; + +/** @} */ +}; + + +struct length { +/** + * @defgroup cpp_addons_units_length Length + * @ingroup cpp_addons_units + * @{ + */ + +struct Meters { }; +struct PicoMeters { }; +struct NanoMeters { }; +struct MicroMeters { }; +struct MilliMeters { }; +struct CentiMeters { }; +struct KiloMeters { }; +struct Miles { }; +struct Pixels { }; + +/** @} */ +}; + + +struct pressure { +/** + * @defgroup cpp_addons_units_pressure Pressure + * @ingroup cpp_addons_units + * @{ + */ + +struct Pascal { }; +struct Bar { }; + +/** @} */ +}; + + +struct speed { +/** + * @defgroup cpp_addons_units_speed Speed + * @ingroup cpp_addons_units + * @{ + */ + +struct MetersPerSecond { }; +struct KiloMetersPerSecond { }; +struct KiloMetersPerHour { }; +struct MilesPerHour { }; + +/** @} */ +}; + + +struct temperature { +/** + * @defgroup cpp_addons_units_temperature Temperature + * @ingroup cpp_addons_units + * @{ + */ + +struct Kelvin { }; +struct Celsius { }; +struct Fahrenheit { }; + +/** @} */ +}; + + +struct data { +/** + * @defgroup cpp_addons_units_data Data + * @ingroup cpp_addons_units + * @{ + */ + +struct Bits { }; +struct KiloBits { }; +struct MegaBits { }; +struct GigaBits { }; +struct Bytes { }; +struct KiloBytes { }; +struct MegaBytes { }; +struct GigaBytes { }; +struct KibiBytes { }; +struct MebiBytes { }; +struct GibiBytes { }; + +/** @} */ +}; + +struct datarate { +/** + * @defgroup cpp_addons_units_datarate Data Rate + * @ingroup cpp_addons_units + * @{ + */ + +struct BitsPerSecond { }; +struct KiloBitsPerSecond { }; +struct MegaBitsPerSecond { }; +struct GigaBitsPerSecond { }; +struct BytesPerSecond { }; +struct KiloBytesPerSecond { }; +struct MegaBytesPerSecond { }; +struct GigaBytesPerSecond { }; + +/** @} */ +}; + + +struct frequency { +/** + * @defgroup cpp_addons_units_frequency Frequency + * @ingroup cpp_addons_units + * @{ + */ + +struct Hertz { }; +struct KiloHertz { }; +struct MegaHertz { }; +struct GigaHertz { }; + +/** @} */ +}; + + +struct uri { +/** + * @defgroup cpp_addons_units_uri Uri + * @ingroup cpp_addons_units + * @{ + */ + +struct Hyperlink { }; +struct Image { }; +struct File { }; + +/** @} */ +}; + + +struct Percentage { }; +struct Bel { }; +struct DeciBel { }; + +units(flecs::world& world); + +/** @} */ + +}; +} + +#endif +#ifdef FLECS_MONITOR +/** + * @file addons/cpp/mixins/monitor/decl.hpp + * @brief Monitor module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_monitor Monitor + * @ingroup cpp_addons + * The monitor addon periodically tracks statistics for the world and systems. + * + * @{ + */ + +/** Component that stores world statistics */ +using WorldStats = EcsWorldStats; + +/** Component that stores system/pipeline statistics */ +using PipelineStats = EcsPipelineStats; + +struct monitor { + monitor(flecs::world& world); +}; + +/** @} */ + +} + +#endif +#ifdef FLECS_METRICS +/** + * @file addons/cpp/mixins/metrics/decl.hpp + * @brief Metrics declarations. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/metrics/builder.hpp + * @brief Metric builder. + */ + +#pragma once + +#define ECS_EVENT_DESC_ID_COUNT_MAX (8) + +namespace flecs { + +/** + * @ingroup cpp_addons_metrics + * @{ + */ + +/** Event builder interface */ +struct metric_builder { + metric_builder(flecs::world_t *world, flecs::entity_t entity) + : m_world(world) + { + m_desc.entity = entity; + } + + ~metric_builder(); + + metric_builder& member(flecs::entity_t e) { + m_desc.member = e; + return *this; + } + + metric_builder& member(const char *name); + + template + metric_builder& member(const char *name); + + metric_builder& dotmember(const char *name); + + template + metric_builder& dotmember(const char *name); + + metric_builder& id(flecs::id_t the_id) { + m_desc.id = the_id; + return *this; + } + + metric_builder& id(flecs::entity_t first, flecs::entity_t second) { + m_desc.id = ecs_pair(first, second); + return *this; + } + + template + metric_builder& id() { + return id(_::cpp_type::id(m_world)); + } + + template + metric_builder& id(flecs::entity_t second) { + return id(_::cpp_type::id(m_world), second); + } + + template + metric_builder& id_second(flecs::entity_t first) { + return id(first, _::cpp_type::id(m_world)); + } + + template + metric_builder& id() { + return id(_::cpp_type::id(m_world)); + } + + metric_builder& targets(bool value = true) { + m_desc.targets = value; + return *this; + } + + metric_builder& kind(flecs::entity_t the_kind) { + m_desc.kind = the_kind; + return *this; + } + + template + metric_builder& kind() { + return kind(_::cpp_type::id(m_world)); + } + + metric_builder& brief(const char *b) { + m_desc.brief = b; + return *this; + } + + operator flecs::entity(); + +protected: + flecs::world_t *m_world; + ecs_metric_desc_t m_desc = {}; + bool m_created = false; +}; + +/** + * @} + */ + +} + + +namespace flecs { + +/** + * @defgroup cpp_addons_metrics Metrics + * @ingroup cpp_addons + * The metrics module extracts metrics from components and makes them available + * through a unified component interface. + * + * @{ + */ + +struct metrics { + using Value = EcsMetricValue; + using Source = EcsMetricSource; + + struct Instance { }; + struct Metric { }; + struct Counter { }; + struct CounterIncrement { }; + struct CounterId { }; + struct Gauge { }; + + metrics(flecs::world& world); +}; + +/** @} */ + +} + +#endif +#ifdef FLECS_ALERTS +/** + * @file addons/cpp/mixins/alerts/decl.hpp + * @brief Alert declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_alerts Alerts + * @ingroup cpp_addons + * Alert implementation. + * + * @{ + */ + +/** Module */ +struct alerts { + using AlertsActive = EcsAlertsActive; + using Instance = EcsAlertInstance; + + struct Alert { }; + struct Info { }; + struct Warning { }; + struct Error { }; + + alerts(flecs::world& world); +}; + +template +struct alert; + +template +struct alert_builder; + +/** @} */ + +} + +#endif +#ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/decl.hpp + * @brief JSON addon declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_json Json + * @ingroup cpp_addons + * Functions for serializing to/from JSON. + * + * @{ + */ + +using from_json_desc_t = ecs_from_json_desc_t; +using entity_to_json_desc_t = ecs_entity_to_json_desc_t; +using iter_to_json_desc_t = ecs_iter_to_json_desc_t; + +/** @} */ + +} + +#endif +#ifdef FLECS_APP +/** + * @file addons/cpp/mixins/app/decl.hpp + * @brief App addon declarations. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/app/builder.hpp + * @brief App builder. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_app App + * @ingroup cpp_addons + * Optional addon for running the main application loop. + * + * @{ + */ + +/** App builder interface */ +struct app_builder { + app_builder(flecs::world_t *world) + : m_world(world) + , m_desc{} + { + const ecs_world_info_t *stats = ecs_get_world_info(world); + m_desc.target_fps = stats->target_fps; + ecs_ftime_t t_zero = 0.0; + if (ECS_EQ(m_desc.target_fps, t_zero)) { + m_desc.target_fps = 60; + } + } + + app_builder& target_fps(ecs_ftime_t value) { + m_desc.target_fps = value; + return *this; + } + + app_builder& delta_time(ecs_ftime_t value) { + m_desc.delta_time = value; + return *this; + } + + app_builder& threads(int32_t value) { + m_desc.threads = value; + return *this; + } + + app_builder& frames(int32_t value) { + m_desc.frames = value; + return *this; + } + + app_builder& enable_rest(uint16_t port = 0) { + m_desc.enable_rest = true; + m_desc.port = port; + return *this; + } + + app_builder& enable_monitor(bool value = true) { + m_desc.enable_monitor = value; + return *this; + } + + app_builder& init(ecs_app_init_action_t value) { + m_desc.init = value; + return *this; + } + + app_builder& ctx(void *value) { + m_desc.ctx = value; + return *this; + } + + int run() { + int result = ecs_app_run(m_world, &m_desc); + if (ecs_should_quit(m_world)) { + // Only free world if quit flag is set. This ensures that we won't + // try to cleanup the world if the app is used in an environment + // that takes over the main loop, like with emscripten. + ecs_fini(m_world); + } + return result; + } + +private: + flecs::world_t *m_world; + ecs_app_desc_t m_desc; +}; + +/** @} */ + +} + + +#endif + +/** + * @file addons/cpp/log.hpp + * @brief Logging functions. + */ + +#pragma once + +namespace flecs { +namespace log { + +/** + * @defgroup cpp_log Logging + * @ingroup cpp_addons + * Logging functions. + * + * @{ + */ + +/** Set log level */ +inline void set_level(int level) { + ecs_log_set_level(level); +} + +inline int get_level() { + return ecs_log_get_level(); +} + +/** Enable colors in logging */ +inline void enable_colors(bool enabled = true) { + ecs_log_enable_colors(enabled); +} + +/** Enable timestamps in logging */ +inline void enable_timestamp(bool enabled = true) { + ecs_log_enable_timestamp(enabled); +} + +/** Enable time delta in logging */ +inline void enable_timedelta(bool enabled = true) { + ecs_log_enable_timedelta(enabled); +} + +/** Debug trace (level 1) */ +inline void dbg(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(1, fmt, args); + va_end(args); +} + +/** Trace (level 0) */ +inline void trace(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(0, fmt, args); + va_end(args); +} + +/** Trace (level -2) */ +inline void warn(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(-2, fmt, args); + va_end(args); +} + +/** Trace (level -3) */ +inline void err(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(-3, fmt, args); + va_end(args); +} + +/** Increase log indentation */ +inline void push(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(0, fmt, args); + va_end(args); + ecs_log_push(); +} + +/** Increase log indentation */ +inline void push() { + ecs_log_push(); +} + +/** Increase log indentation */ +inline void pop() { + ecs_log_pop(); +} + +/** @} */ + +} +} + +/** + * @file addons/cpp/pair.hpp + * @brief Utilities for working with compile time pairs. + */ + +#pragma once + +namespace flecs { + +namespace _ { + struct pair_base { }; +} // _ + + +/** + * @defgroup cpp_pair_type Pair type + * @ingroup cpp_core + * Compile time utilities for working with relationship pairs. + * + * @{ + */ + +/** Type that represents a pair. + * The pair type can be used to represent a pair at compile time, and is able + * to automatically derive the storage type associated with the pair, accessible + * through pair::type. + * + * The storage type is derived using the following rules: + * - if pair::first is non-empty, the storage type is pair::first + * - if pair::first is empty and pair::second is non-empty, the storage type is pair::second + * + * The pair type can hold a temporary value so that it can be used in the + * signatures of queries + */ +template +struct pair : _::pair_base { + using type = conditional_t::value || is_empty::value, First, Second>; + using first = First; + using second = Second; + + pair(type& v) : ref_(v) { } + + // This allows the class to be used as a temporary object + pair(const type& v) : ref_(const_cast(v)) { } + + operator type&() { + return ref_; + } + + operator const type&() const { + return ref_; + } + + type* operator->() { + return &ref_; + } + + const type* operator->() const { + return &ref_; + } + + type& operator*() { + return ref_; + } + + const type& operator*() const { + return ref_; + } + +private: + type& ref_; +}; + +template ::value> = 0> +using pair_object = pair; + +template +using raw_type_t = remove_pointer_t>; + +/** Test if type is a pair. */ +template +struct is_pair { + static constexpr bool value = is_base_of<_::pair_base, raw_type_t >::value; +}; + +/** Get pair::first from pair while preserving cv qualifiers. */ +template +using pair_first_t = transcribe_cv_t, typename raw_type_t

::first>; + +/** Get pair::second from pair while preserving cv qualifiers. */ +template +using pair_second_t = transcribe_cv_t, typename raw_type_t

::second>; + +/** Get pair::type type from pair while preserving cv qualifiers and pointer type. */ +template +using pair_type_t = transcribe_cvp_t, typename raw_type_t

::type>; + +/** Get actual type from a regular type or pair. */ +template +struct actual_type; + +template +struct actual_type::value >> { + using type = T; +}; + +template +struct actual_type::value >> { + using type = pair_type_t; +}; + +template +using actual_type_t = typename actual_type::type; + + +// Get type without const, *, & +template +struct base_type { + using type = decay_t< remove_pointer_t< actual_type_t > >; +}; + +template +using base_type_t = typename base_type::type; + + +// Get type without *, & (retains const which is useful for function args) +template +struct base_arg_type { + using type = remove_pointer_t< remove_reference_t< actual_type_t > >; +}; + +template +using base_arg_type_t = typename base_arg_type::type; + + +// Test if type is the same as its actual type +template +struct is_actual { + static constexpr bool value = + std::is_same >::value && !is_enum::value; +}; + +} // flecs + +/** + * @file addons/cpp/lifecycle_traits.hpp + * @brief Utilities for discovering and registering component lifecycle hooks. + */ + +#pragma once + +namespace flecs +{ + +namespace _ +{ + +inline void ecs_ctor_illegal(void *, int32_t, const ecs_type_info_t *ti) { + ecs_abort(ECS_INVALID_OPERATION, "invalid constructor for %s", ti->name); +} + +inline void ecs_dtor_illegal(void *, int32_t, const ecs_type_info_t *ti) { + ecs_abort(ECS_INVALID_OPERATION, "invalid destructor for %s", ti->name); +} + +inline void ecs_copy_illegal( + void *, const void *, int32_t, const ecs_type_info_t *ti) +{ + ecs_abort(ECS_INVALID_OPERATION, "invalid copy assignment for %s", ti->name); +} + +inline void ecs_move_illegal(void *, void *, int32_t, const ecs_type_info_t *ti) { + ecs_abort(ECS_INVALID_OPERATION, "invalid move assignment for %s", ti->name); +} + +inline void ecs_copy_ctor_illegal( + void *, const void *, int32_t, const ecs_type_info_t *ti) +{ + ecs_abort(ECS_INVALID_OPERATION, "invalid copy construct for %s", ti->name); +} + +inline void ecs_move_ctor_illegal( + void *, void *, int32_t, const ecs_type_info_t *ti) +{ + ecs_abort(ECS_INVALID_OPERATION, "invalid move construct for %s", ti->name); +} + + +// T() +// Can't coexist with T(flecs::entity) or T(flecs::world, flecs::entity) +template +void ctor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast(ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&arr[i], T); + } +} + +// ~T() +template +void dtor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast(ptr); + for (int i = 0; i < count; i ++) { + arr[i].~T(); + } +} + +// T& operator=(const T&) +template +void copy_impl(void *dst_ptr, const void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + const T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = src_arr[i]; + } +} + +// T& operator=(T&&) +template +void move_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = FLECS_MOV(src_arr[i]); + } +} + +// T(T&) +template +void copy_ctor_impl(void *dst_ptr, const void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + const T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(src_arr[i])); + } +} + +// T(T&&) +template +void move_ctor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); + } +} + +// T(T&&), ~T() +// Typically used when moving to a new table, and removing from the old table +template +void ctor_move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); + src_arr[i].~T(); + } +} + +// Move assign + dtor (non-trivial move assignment) +// Typically used when moving a component to a deleted component +template ::value > = 0> +void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + // Move assignment should free dst & assign dst to src + dst_arr[i] = FLECS_MOV(src_arr[i]); + // Destruct src. Move should have left object in a state where it no + // longer holds resources, but it still needs to be destructed. + src_arr[i].~T(); + } +} + +// Move assign + dtor (trivial move assignment) +// Typically used when moving a component to a deleted component +template ::value > = 0> +void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + // Cleanup resources of dst + dst_arr[i].~T(); + // Copy src to dst + dst_arr[i] = FLECS_MOV(src_arr[i]); + // No need to destruct src. Since this is a trivial move the code + // should be agnostic to the address of the component which means we + // can pretend nothing got destructed. + } +} + +} // _ + +// Trait to test if type is constructible by flecs +template +struct is_flecs_constructible { + static constexpr bool value = + std::is_default_constructible>::value; +}; + +namespace _ +{ + +// Trivially constructible +template ::value > = 0> +ecs_xtor_t ctor() { + return nullptr; +} + +// Not constructible by flecs +template ::value > = 0> +ecs_xtor_t ctor() { + return ecs_ctor_illegal; +} + +// Default constructible +template ::value && + std::is_default_constructible::value > = 0> +ecs_xtor_t ctor() { + return ctor_impl; +} + +// No dtor +template ::value > = 0> +ecs_xtor_t dtor() { + return nullptr; +} + +// Dtor +template ::value && + ! std::is_trivially_destructible::value > = 0> +ecs_xtor_t dtor() { + return dtor_impl; +} + +// Assert when the type cannot be destructed +template ::value > = 0> +ecs_xtor_t dtor() { + flecs_static_assert(always_false::value, + "component type must be destructible"); + return ecs_dtor_illegal; +} + +// Trivially copyable +template ::value > = 0> +ecs_copy_t copy() { + return nullptr; +} + +// Not copyable +template ::value && + ! std::is_copy_assignable::value > = 0> +ecs_copy_t copy() { + return ecs_copy_illegal; +} + +// Copy assignment +template ::value && + ! std::is_trivially_copyable::value > = 0> +ecs_copy_t copy() { + return copy_impl; +} + +// Trivially move assignable +template ::value > = 0> +ecs_move_t move() { + return nullptr; +} + +// Component types must be move assignable +template ::value > = 0> +ecs_move_t move() { + flecs_static_assert(always_false::value, + "component type must be move assignable"); + return ecs_move_illegal; +} + +// Move assignment +template ::value && + ! std::is_trivially_move_assignable::value > = 0> +ecs_move_t move() { + return move_impl; +} + +// Trivially copy constructible +template ::value > = 0> +ecs_copy_t copy_ctor() { + return nullptr; +} + +// No copy ctor +template ::value > = 0> +ecs_copy_t copy_ctor() { + return ecs_copy_ctor_illegal; +} + +// Copy ctor +template ::value && + ! std::is_trivially_copy_constructible::value > = 0> +ecs_copy_t copy_ctor() { + return copy_ctor_impl; +} + +// Trivially move constructible +template ::value > = 0> +ecs_move_t move_ctor() { + return nullptr; +} + +// Component types must be move constructible +template ::value > = 0> +ecs_move_t move_ctor() { + flecs_static_assert(always_false::value, + "component type must be move constructible"); + return ecs_move_ctor_illegal; +} + +// Move ctor +template ::value && + ! std::is_trivially_move_constructible::value > = 0> +ecs_move_t move_ctor() { + return move_ctor_impl; +} + +// Trivial merge (move assign + dtor) +template ::value && + std::is_trivially_destructible::value > = 0> +ecs_move_t ctor_move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template ::value || + ! std::is_destructible::value > = 0> +ecs_move_t ctor_move_dtor() { + flecs_static_assert(always_false::value, + "component type must be move constructible and destructible"); + return ecs_move_ctor_illegal; +} + +// Merge ctor + dtor +template ::value && + std::is_trivially_destructible::value) && + std::is_move_constructible::value && + std::is_destructible::value > = 0> +ecs_move_t ctor_move_dtor() { + return ctor_move_dtor_impl; +} + +// Trivial merge (move assign + dtor) +template ::value && + std::is_trivially_destructible::value > = 0> +ecs_move_t move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template ::value || + ! std::is_destructible::value > = 0> +ecs_move_t move_dtor() { + flecs_static_assert(always_false::value, + "component type must be move constructible and destructible"); + return ecs_move_ctor_illegal; +} + +// Merge assign + dtor +template ::value && + std::is_trivially_destructible::value) && + std::is_move_assignable::value && + std::is_destructible::value > = 0> +ecs_move_t move_dtor() { + return move_dtor_impl; +} + +} // _ +} // flecs + +/** + * @file addons/cpp/ref.hpp + * @brief Class that caches data to speedup get operations. + */ + +#pragma once + +namespace flecs +{ + +/** + * @defgroup cpp_ref Refs + * @ingroup cpp_core + * Refs are a fast mechanism for referring to a specific entity/component. + * + * @{ + */ + +/** Component reference. + * Reference to a component from a specific entity. + */ +template +struct ref { + ref() : m_world(nullptr), m_ref{} { } + + ref(world_t *world, entity_t entity, flecs::id_t id = 0) + : m_ref() + { + // the world we were called with may be a stage; convert it to a world + // here if that is the case + m_world = world ? const_cast(ecs_get_world(world)) + : nullptr; + if (!id) { + id = _::cpp_type::id(world); + } + + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + + m_ref = ecs_ref_init_id(m_world, entity, id); + } + + T* operator->() { + T* result = static_cast(ecs_ref_get_id( + m_world, &m_ref, this->m_ref.id)); + + ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); + + return result; + } + + T* get() { + return static_cast(ecs_ref_get_id( + m_world, &m_ref, this->m_ref.id)); + } + + T* try_get() { + if (!m_world || !m_ref.entity) { + return nullptr; + } + + return get(); + } + + flecs::entity entity() const; + +private: + world_t *m_world; + flecs::ref_t m_ref; +}; + +/** @} */ + +} + +/** + * @file addons/cpp/world.hpp + * @brief World class. + */ + +#pragma once + +namespace flecs +{ + +/* Static helper functions to assign a component value */ + +// set(T&&), T = constructible +template ::value > = 0> +inline void set(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + + if (!ecs_is_deferred(world)) { + T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); + dst = FLECS_MOV(value); + + ecs_modified_id(world, entity, id); + } else { + T& dst = *static_cast(ecs_get_mut_modified_id(world, entity, id)); + dst = FLECS_MOV(value); + } +} + +// set(const T&), T = constructible +template ::value > = 0> +inline void set(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + + if (!ecs_is_deferred(world)) { + T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); + dst = FLECS_MOV(value); + + ecs_modified_id(world, entity, id); + } else { + T& dst = *static_cast(ecs_get_mut_modified_id(world, entity, id)); + dst = FLECS_MOV(value); + } +} + +// set(T&&), T = not constructible +template ::value > = 0> +inline void set(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + + if (!ecs_is_deferred(world)) { + T& dst = *static_cast*>(ecs_get_mut_id(world, entity, id)); + dst = FLECS_MOV(value); + + ecs_modified_id(world, entity, id); + } else { + T& dst = *static_cast*>(ecs_get_mut_modified_id(world, entity, id)); + dst = FLECS_MOV(value); + } +} + +// set(const T&), T = not constructible +template ::value > = 0> +inline void set(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + + if (!ecs_is_deferred(world)) { + T& dst = *static_cast*>(ecs_get_mut_id(world, entity, id)); + dst = FLECS_MOV(value); + + ecs_modified_id(world, entity, id); + } else { + T& dst = *static_cast*>(ecs_get_mut_modified_id(world, entity, id)); + dst = FLECS_MOV(value); + } +} + +// emplace for T(Args...) +template , Args...>::value || + std::is_default_constructible>::value > = 0> +inline void emplace(world_t *world, flecs::entity_t entity, flecs::id_t id, Args&&... args) { + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + T& dst = *static_cast(ecs_emplace_id(world, entity, id)); + + FLECS_PLACEMENT_NEW(&dst, T{FLECS_FWD(args)...}); + + ecs_modified_id(world, entity, id); +} + +// set(T&&) +template +inline void set(world_t *world, entity_t entity, A&& value) { + id_t id = _::cpp_type::id(world); + flecs::set(world, entity, FLECS_FWD(value), id); +} + +// set(const T&) +template +inline void set(world_t *world, entity_t entity, const A& value) { + id_t id = _::cpp_type::id(world); + flecs::set(world, entity, value, id); +} + +/** Return id without generation. + * + * @see ecs_strip_generation + */ +inline flecs::id_t strip_generation(flecs::entity_t e) { + return ecs_strip_generation(e); +} + +/** Return entity generation. + */ +inline uint32_t get_generation(flecs::entity_t e) { + return ECS_GENERATION(e); +} + +struct scoped_world; + +/** + * @defgroup cpp_world World + * @ingroup cpp_core + * World operations. + * + * @{ + */ + +/** 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. + */ +struct world { + /** Create world. + */ + explicit 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. + */ + explicit 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) noexcept { + m_world = obj.m_world; + m_owned = obj.m_owned; + obj.m_world = nullptr; + obj.m_owned = false; + } + + /* Implicit conversion to world_t* */ + operator world_t*() const { return m_world; } + + /** Not allowed to copy a world. May only take a reference. + */ + world& operator=(const world& obj) = delete; + + world& operator=(world&& obj) noexcept { + 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 && ecs_stage_is_async(m_world)) { + ecs_async_stage_free(m_world); + } else + if (m_owned && m_world) { + ecs_fini(m_world); + } + } + + /** Deletes and recreates the world. */ + void reset() { + // Can only reset the world if we own the world object. + ecs_assert(this->m_owned, ECS_INVALID_OPERATION, NULL); + ecs_fini(m_world); + m_world = ecs_init(); + } + + /** Obtain pointer to C world object. + */ + world_t* c_ptr() const { + return m_world; + } + + /** Signal application should quit. + * After calling this operation, the next call to progress() returns false. + */ + void quit() const { + ecs_quit(m_world); + } + + /** Register action to be executed when world is destroyed. + */ + void atfini(ecs_fini_action_t action, void *ctx) const { + ecs_atfini(m_world, action, ctx); + } + + /** Test if quit() has been called. + */ + bool should_quit() const { + return ecs_should_quit(m_world); + } + + /** Begin frame. + * When an application does not use progress() to control the main loop, it + * can still use Flecs features such as FPS limiting and time measurements. + * This operation needs to be invoked whenever a new frame is about to get + * processed. + * + * Calls to frame_begin must always be followed by frame_end. + * + * The function accepts a delta_time parameter, which will get passed to + * systems. This value is also used to compute the amount of time the + * function needs to sleep to ensure it does not exceed the target_fps, when + * it is set. When 0 is provided for delta_time, the time will be measured. + * + * This function should only be ran from the main thread. + * + * @param delta_time Time elapsed since the last frame. + * @return The provided delta_time, or measured time if 0 was provided. + */ + ecs_ftime_t frame_begin(float delta_time = 0) const { + return ecs_frame_begin(m_world, delta_time); + } + + /** End frame. + * This operation must be called at the end of the frame, and always after + * ecs_frame_begin. + * + * This function should only be ran from the main thread. + */ + void frame_end() const { + ecs_frame_end(m_world); + } + + /** Begin staging. + * When an application does not use ecs_progress to control the main loop, it + * can still use Flecs features such as the defer queue. When an application + * needs to stage changes, it needs to call this function after ecs_frame_begin. + * A call to ecs_readonly_begin must be followed by a call to ecs_readonly_end. + * + * 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_readonly_end is invoked. + * + * While the world is in staging mode, no structural changes (add/remove/...) + * can be made to the world itself. Operations must be executed on a stage + * instead (see ecs_get_stage). + * + * This function should only be ran from the main thread. + * + * @return Whether world is currently staged. + */ + bool readonly_begin() const { + return ecs_readonly_begin(m_world); + } + + /** End staging. + * Leaves staging mode. After this operation the world may be directly mutated + * again. By default this operation also merges data back into the world, unless + * auto-merging was disabled explicitly. + * + * This function should only be ran from the main thread. + */ + void readonly_end() const { + ecs_readonly_end(m_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. + */ + bool defer_begin() const { + return ecs_defer_begin(m_world); + } + + /** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + */ + bool defer_end() const { + return ecs_defer_end(m_world); + } + + /** Test whether deferring is enabled. + */ + bool is_deferred() const { + return ecs_is_deferred(m_world); + } + + /** Configure world to have N stages. + * This initializes N stages, which allows applications to defer operations to + * multiple isolated defer queues. This is typically used for applications with + * multiple threads, where each thread gets its own queue, and commands are + * merged when threads are synchronized. + * + * Note that set_threads() already creates the appropriate number of stages. + * The set_stage_count() operation is useful for applications that want to manage + * their own stages and/or threads. + * + * @param stages The number of stages. + */ + void set_stage_count(int32_t stages) const { + ecs_set_stage_count(m_world, stages); + } + + /** Get number of configured stages. + * Return number of stages set by set_stage_count. + * + * @return The number of stages used for threading. + */ + int32_t get_stage_count() const { + return ecs_get_stage_count(m_world); + } + + /** Get current stage id. + * The stage id can be used by an application to learn about which stage it + * is using, which typically corresponds with the worker thread id. + * + * @return The stage id. + */ + int32_t get_stage_id() const { + return ecs_get_stage_id(m_world); + } + + /** Test if is a stage. + * If this function returns false, it is guaranteed that this is a valid + * world object. + * + * @return True if the world is a stage, false if not. + */ + bool is_stage() const { + ecs_assert( + ecs_poly_is(m_world, ecs_world_t) || + ecs_poly_is(m_world, ecs_stage_t), + ECS_INVALID_PARAMETER, NULL); + return ecs_poly_is(m_world, ecs_stage_t); + } + + /** Enable/disable auto-merging for world or stage. + * When auto-merging is enabled, staged data will automatically be merged + * with the world when staging ends. This happens at the end of progress(), + * at a sync point or when readonly_end() is called. + * + * Applications can exercise more control over when data from a stage is + * merged by disabling auto-merging. This requires an application to + * explicitly call merge() on the stage. + * + * When this function is invoked on the world, it sets all current stages to + * the provided value and sets the default for new stages. When this + * function is invoked on a stage, auto-merging is only set for that specific + * stage. + * + * @param automerge Whether to enable or disable auto-merging. + */ + void set_automerge(bool automerge) const { + ecs_set_automerge(m_world, automerge); + } + + /** Merge world or stage. + * When automatic merging is disabled, an application can call this + * operation on either an individual stage, or on the world which will merge + * all stages. This operation may only be called when staging is not enabled + * (either after progress() or after readonly_end()). + * + * This operation may be called on an already merged stage or world. + */ + void merge() const { + ecs_merge(m_world); + } + + /** Get stage-specific world pointer. + * Flecs threads can safely invoke the API as long as they have a private + * context to write to, also referred to as the stage. This function returns a + * pointer to a stage, disguised as a world pointer. + * + * Note that this function does not(!) create a new world. It simply wraps the + * existing world in a thread-specific context, which the API knows how to + * unwrap. The reason the stage is returned as an ecs_world_t is so that it + * can be passed transparently to the existing API functions, vs. having to + * create a dediated API for threading. + * + * @param stage_id The index of the stage to retrieve. + * @return A thread-specific pointer to the world. + */ + flecs::world get_stage(int32_t stage_id) const { + return flecs::world(ecs_get_stage(m_world, stage_id)); + } + + /** Create asynchronous stage. + * An asynchronous stage can be used to asynchronously queue operations for + * later merging with the world. An asynchronous stage is similar to a regular + * stage, except that it does not allow reading from the world. + * + * Asynchronous stages are never merged automatically, and must therefore be + * manually merged with the ecs_merge function. It is not necessary to call + * defer_begin or defer_end before and after enqueuing commands, as an + * asynchronous stage unconditionally defers operations. + * + * The application must ensure that no commands are added to the stage while the + * stage is being merged. + * + * An asynchronous stage must be cleaned up by ecs_async_stage_free. + * + * @return The stage. + */ + flecs::world async_stage() const { + auto result = flecs::world(ecs_async_stage_new(m_world)); + result.m_owned = true; + return result; + } + + /** Get actual world. + * If the current object points to a stage, this operation will return the + * actual world. + * + * @return The actual world. + */ + flecs::world get_world() const { + /* Safe cast, mutability is checked */ + return flecs::world( + m_world ? const_cast(ecs_get_world(m_world)) : nullptr); + } + + /** Test whether the current world object is readonly. + * This function allows the code to test whether the currently used world + * object is readonly or whether it allows for writing. + * + * @return True if the world or stage is readonly. + */ + bool is_readonly() const { + return ecs_stage_is_readonly(m_world); + } + + /** 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_ctx(void* ctx, ecs_ctx_free_t ctx_free = nullptr) const { + ecs_set_ctx(m_world, ctx, ctx_free); + } + + /** Get world context. + * + * @return The configured world context. + */ + void* get_ctx() const { + return ecs_get_ctx(m_world); + } + + /** Set world binding 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_binding_ctx(void* ctx, ecs_ctx_free_t ctx_free = nullptr) const { + ecs_set_binding_ctx(m_world, ctx, ctx_free); + } + + /** Get world binding context. + * + * @return The configured world context. + */ + void* get_binding_ctx() const { + return ecs_get_binding_ctx(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(int32_t entity_count) const { + ecs_dim(m_world, 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); + } + + /** Set current scope. + * + * @param scope The scope to set. + * @return The current scope; + * @see ecs_set_scope + */ + flecs::entity set_scope(const flecs::entity_t scope) const; + + /** Get current scope. + * + * @return The current scope. + * * @see ecs_get_scope + */ + flecs::entity get_scope() const; + + /** Same as set_scope but with type. + * * @see ecs_set_scope + */ + template + flecs::entity set_scope() const; + + /** Set search path. + * @see ecs_set_lookup_path + */ + flecs::entity_t* set_lookup_path(const flecs::entity_t *search_path) const { + return ecs_set_lookup_path(m_world, search_path); + } + + /** Lookup entity by name. + * + * @param name Entity name. + * @param search_path When false, only the current scope is searched. + * @result The entity if found, or 0 if not found. + */ + flecs::entity lookup(const char *name, bool search_path = true) const; + + /** Set singleton component. + */ + template ::value > = 0> + void set(const T& value) const { + flecs::set(m_world, _::cpp_type::id(m_world), value); + } + + /** Set singleton component. + */ + template ::value > = 0> + void set(T&& value) const { + flecs::set(m_world, _::cpp_type::id(m_world), + FLECS_FWD(value)); + } + + /** Set singleton pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + void set(const A& value) const { + flecs::set

(m_world, _::cpp_type::id(m_world), value); + } + + /** Set singleton pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + void set(A&& value) const { + flecs::set

(m_world, _::cpp_type::id(m_world), FLECS_FWD(value)); + } + + /** Set singleton pair. + */ + template + void set(Second second, const First& value) const; + + /** Set singleton pair. + */ + template + void set(Second second, First&& value) const; + + /** Set singleton component inside a callback. + */ + template ::value > = 0 > + void set(const Func& func) const; + + template + void emplace(Args&&... args) const { + flecs::id_t component_id = _::cpp_type::id(m_world); + flecs::emplace(m_world, component_id, component_id, + FLECS_FWD(args)...); + } + + /** Get mut singleton component. + */ + template + T* get_mut() const; + + /** Mark singleton component as modified. + */ + template + void modified() const; + + /** Get ref singleton component. + */ + template + ref get_ref() const; + + /** Get singleton component. + */ + template + const T* get() const; + + /** Get singleton pair. + */ + template , + typename A = actual_type_t

> + const A* get() const; + + /** Get singleton pair. + */ + template + const First* get(Second second) const; + + /** Get singleton component inside a callback. + */ + template ::value > = 0 > + void get(const Func& func) const; + + /** Test if world has singleton component. + */ + template + bool has() const; + + /** Test if world has the provided pair. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + */ + template + bool has() const; + + /** Test if world has the provided pair. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + */ + template + bool has(flecs::id_t second) const; + + /** Test if world has the provided pair. + * + * @param first The first element of the pair + * @param second The second element of the pair + */ + bool has(flecs::id_t first, flecs::id_t second) const; + + /** Add singleton component. + */ + template + void add() const; + + /** Adds a pair to the singleton component. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + */ + template + void add() const; + + /** Adds a pair to the singleton component. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + */ + template + void add(flecs::entity_t second) const; + + /** Adds a pair to the singleton entity. + * + * @param first The first element of the pair + * @param second The second element of the pair + */ + void add(flecs::entity_t first, flecs::entity_t second) const; + + /** Remove singleton component. + */ + template + void remove() const; + + /** Removes the pair singleton component. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + */ + template + void remove() const; + + /** Removes the pair singleton component. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + */ + template + void remove(flecs::entity_t second) const; + + /** Removes the pair singleton component. + * + * @param first The first element of the pair + * @param second The second element of the pair + */ + void remove(flecs::entity_t first, flecs::entity_t second) const; + + /** Iterate entities in root of world + * Accepts a callback with the following signature: + * + * @code + * void(*)(flecs::entity e); + * @endcode + */ + template + void children(Func&& f) const; + + /** Get singleton entity for type. + */ + template + flecs::entity singleton() const; + + /** Get target for a given pair from a singleton entity. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @tparam First The first element of the pair. + * @param index The index (0 for the first instance of the relationship). + */ + template + flecs::entity target(int32_t index = 0) const; + + /** Get target for a given pair from a singleton entity. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @param first The first element of the pair for which to retrieve the target. + * @param index The index (0 for the first instance of the relationship). + */ + template + flecs::entity target(flecs::entity_t first, int32_t index = 0) const; + + /** Get target for a given pair from a singleton entity. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @param first The first element of the pair for which to retrieve the target. + * @param index The index (0 for the first instance of the relationship). + */ + flecs::entity target(flecs::entity_t first, int32_t index = 0) const; + + /** Create alias for component. + * + * @tparam T to create an alias for. + * @param alias Alias for the component. + * @return Entity representing the component. + */ + template + flecs::entity use(const char *alias = nullptr) const; + + /** 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) const; + + /** 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) const; + + /** Count entities matching a component. + * + * @param component_id The component id. + */ + int count(flecs::id_t component_id) const { + return ecs_count_id(m_world, component_id); + } + + /** Count entities matching a pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + int count(flecs::entity_t first, flecs::entity_t second) const { + return ecs_count_id(m_world, ecs_pair(first, second)); + } + + /** Count entities matching a component. + * + * @tparam T The component type. + */ + template + int count() const { + return count(_::cpp_type::id(m_world)); + } + + /** Count entities matching a pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + int count(flecs::entity_t second) const { + return count(_::cpp_type::id(m_world), second); + } + + /** Count entities matching a pair. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template + int count() const { + return count( + _::cpp_type::id(m_world), + _::cpp_type::id(m_world)); + } + + /** All entities created in function are created with id. + */ + template + void with(id_t with_id, const Func& func) const { + ecs_id_t prev = ecs_set_with(m_world, with_id); + func(); + ecs_set_with(m_world, prev); + } + + /** All entities created in function are created with type. + */ + template + void with(const Func& func) const { + with(this->id(), func); + } + + /** All entities created in function are created with pair. + */ + template + void with(const Func& func) const { + with(ecs_pair(this->id(), this->id()), func); + } + + /** All entities created in function are created with pair. + */ + template + void with(id_t second, const Func& func) const { + with(ecs_pair(this->id(), second), func); + } + + /** All entities created in function are created with pair. + */ + template + void with(id_t first, id_t second, const Func& func) const { + with(ecs_pair(first, second), func); + } + + /** All entities created in function are created in scope. All operations + * called in function (such as lookup) are relative to scope. + */ + template + void scope(id_t parent, const Func& func) const { + ecs_entity_t prev = ecs_set_scope(m_world, parent); + func(); + ecs_set_scope(m_world, prev); + } + + /** Same as scope(parent, func), but with T as parent. + */ + template + void scope(const Func& func) const { + flecs::id_t parent = _::cpp_type::id(m_world); + scope(parent, func); + } + + /** Use provided scope for operations ran on returned world. + * Operations need to be ran in a single statement. + */ + flecs::scoped_world scope(id_t parent) const; + + template + flecs::scoped_world scope() const; + + flecs::scoped_world scope(const char* name) const; + + /** Delete all entities with specified id. */ + void delete_with(id_t the_id) const { + ecs_delete_with(m_world, the_id); + } + + /** Delete all entities with specified pair. */ + void delete_with(entity_t first, entity_t second) const { + delete_with(ecs_pair(first, second)); + } + + /** Delete all entities with specified component. */ + template + void delete_with() const { + delete_with(_::cpp_type::id(m_world)); + } + + /** Delete all entities with specified pair. */ + template + void delete_with() const { + delete_with(_::cpp_type::id(m_world), _::cpp_type::id(m_world)); + } + + /** Delete all entities with specified pair. */ + template + void delete_with(entity_t second) const { + delete_with(_::cpp_type::id(m_world), second); + } + + /** Remove all instances of specified id. */ + void remove_all(id_t the_id) const { + ecs_remove_all(m_world, the_id); + } + + /** Remove all instances of specified pair. */ + void remove_all(entity_t first, entity_t second) const { + remove_all(ecs_pair(first, second)); + } + + /** Remove all instances of specified component. */ + template + void remove_all() const { + remove_all(_::cpp_type::id(m_world)); + } + + /** Remove all instances of specified pair. */ + template + void remove_all() const { + remove_all(_::cpp_type::id(m_world), _::cpp_type::id(m_world)); + } + + /** Remove all instances of specified pair. */ + template + void remove_all(entity_t second) const { + remove_all(_::cpp_type::id(m_world), second); + } + + /** Defer all operations called in function. If the world is already in + * deferred mode, do nothing. + */ + template + void defer(const Func& func) const { + ecs_defer_begin(m_world); + func(); + ecs_defer_end(m_world); + } + + /** Suspend deferring operations. + * + * @see ecs_defer_suspend + */ + void defer_suspend() const { + ecs_defer_suspend(m_world); + } + + /** Resume deferring operations. + * + * @see ecs_defer_suspend + */ + void defer_resume() const { + ecs_defer_resume(m_world); + } + + /** Check if entity id exists in the world. + * + * @see ecs_exists + */ + bool exists(flecs::entity_t e) const { + return ecs_exists(m_world, e); + } + + /** Check if entity id exists in the world. + * + * @see ecs_is_alive + */ + bool is_alive(flecs::entity_t e) const { + return ecs_is_alive(m_world, e); + } + + /** Check if entity id is valid. + * Invalid entities cannot be used with API functions. + * + * @see ecs_is_valid + */ + bool is_valid(flecs::entity_t e) const { + return ecs_is_valid(m_world, e); + } + + /** Get alive entity for id. + * Returns the entity with the current generation. + * + * @see ecs_get_alive + */ + flecs::entity get_alive(flecs::entity_t e) const; + +/* Prevent clashing with Unreal define. Unreal applications will have to use + * ecs_ensure. */ +#ifndef ensure + /** Ensures that entity with provided generation is alive. + * This operation will fail if an entity exists with the same id and a + * different, non-zero generation. + * + * @see ecs_ensure + */ + flecs::entity ensure(flecs::entity_t e) const; +#endif + + /* Run callback after completing frame */ + void run_post_frame(ecs_fini_action_t action, void *ctx) const { + ecs_run_post_frame(m_world, action, ctx); + } + + /** Get the world info. + * @see ecs_get_world_info + */ + const flecs::world_info_t* get_info() const{ + return ecs_get_world_info(m_world); + } + + /** Get delta_time */ + ecs_ftime_t delta_time() const { + return get_info()->delta_time; + } + +/** + * @file addons/cpp/mixins/id/mixin.inl + * @brief Id world mixin. + */ + +/** Get id from a type. + * + * @memberof flecs::world + */ +template +flecs::id id() const; + +/** Id factory. + * + * @memberof flecs::world + */ +template +flecs::id id(Args&&... args) const; + +/** Get pair id from relationship, object. + * + * @memberof flecs::world + */ +template +flecs::id pair() const; + +/** Get pair id from relationship, object. + * + * @memberof flecs::world + */ +template +flecs::id pair(entity_t o) const; + +/** Get pair id from relationship, object. + * + * @memberof flecs::world + */ +flecs::id pair(entity_t r, entity_t o) const; + +/** + * @file addons/cpp/mixins/component/mixin.inl + * @brief Component mixin. + */ + +/** Find or register component. + * + * @ingroup cpp_components + * @memberof flecs::world + */ +template +flecs::component component(Args &&... args) const; + +/** Find or register untyped component. + * Method available on flecs::world class. + * + * @ingroup cpp_components + * @memberof flecs::world + */ +template +flecs::untyped_component component(Args &&... args) const; + +/** + * @file addons/cpp/mixins/entity/mixin.inl + * @brief Entity world mixin. + */ + +/** Create an entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template +flecs::entity entity(Args &&... args) const; + +/** Convert enum constant to entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template ::value > = 0> +flecs::id id(E value) const; + +/** Convert enum constant to entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template ::value > = 0> +flecs::entity entity(E value) const; + +/** Create a prefab. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template +flecs::entity prefab(Args &&... args) const; + +/** Create an entity that's associated with a type. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template +flecs::entity entity(const char *name = nullptr) const; + +/** Create a prefab that's associated with a type. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template +flecs::entity prefab(const char *name = nullptr) const; + +/** + * @file addons/cpp/mixins/event/mixin.inl + * @brief Event world mixin. + */ + +/** + * @defgroup cpp_addons_event Events + * @ingroup cpp_addons + * API for emitting events. + * + * @{ + */ + +/** Create a new event. + * + * @memberof flecs::world + * + * @param evt The event id. + * @return Event builder. + */ +flecs::event_builder event(flecs::entity_t evt) const; + +/** Create a new event. + * + * @memberof flecs::world + * + * @tparam E The event type. + * @return Event builder. + */ +template +flecs::event_builder_typed event() const; + +/** @} */ + +/** + * @file addons/cpp/mixins/term/mixin.inl + * @brief Term world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_core_filters + * + * @{ + */ + +/** Create a term. + * + */ +template +flecs::term term(Args &&... args) const; + +/** Create a term for a (component) type. + */ +template +flecs::term term() const; + +/** Create a term for a pair. + */ +template +flecs::term term() const; + +/** @} */ + +/** + * @file addons/cpp/mixins/filter/mixin.inl + * @brief Filter world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_core_filters + * + * @{ + */ + +/** Create a filter. + * + * @see ecs_filter_init + */ +template +flecs::filter filter(Args &&... args) const; + +/** Create a filter builder. + * + * @see ecs_filter_init + */ +template +flecs::filter_builder filter_builder(Args &&... args) const; + +/** Iterate over all entities with components in argument list of function. + * The function parameter must match the following signature: + * + * @code + * void(*)(T&, U&, ...) + * @endcode + * + * or: + * + * @code + * void(*)(flecs::entity, T&, U&, ...) + * @endcode + * + */ +template +void each(Func&& func) const; + +/** Iterate over all entities with provided component. + * The function parameter must match the following signature: + * + * @code + * void(*)(T&) + * @endcode + * + * or: + * + * @code + * void(*)(flecs::entity, T&) + * @endcode + * + */ +template +void each(Func&& func) const; + +/** Iterate over all entities with provided (component) id. */ +template +void each(flecs::id_t term_id, Func&& func) const; + +/** @} */ + +/** + * @file addons/cpp/mixins/observer/mixin.inl + * @brief Observer world mixin. + */ + +/** Observer builder. + * + * @memberof flecs::world + * @ingroup cpp_observers + * + * @{ + */ + +/** Upcast entity to an observer. + * The provided entity must be an observer. + * + * @param e The entity. + * @return An observer object. + */ +flecs::observer observer(flecs::entity e) const; + +/** Create a new observer. + * + * @tparam Components The components to match on. + * @tparam Args Arguments passed to the constructor of flecs::observer_builder. + * @return Observer builder. + */ +template +flecs::observer_builder observer(Args &&... args) const; + +/** @} */ + +/** + * @file addons/cpp/mixins/query/mixin.inl + * @brief Query world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_core_queries + * + * @{ + */ + +/** Create a query. + * @see ecs_query_init + */ +template +flecs::query query(Args &&... args) const; + +/** Create a subquery. + * @see ecs_query_init + */ +template +flecs::query query(flecs::query_base& parent, Args &&... args) const; + +/** Create a query builder. + * @see ecs_query_init + */ +template +flecs::query_builder query_builder(Args &&... args) const; + +/** @} */ + +/** + * @file addons/cpp/mixins/enum/mixin.inl + * @brief Enum world mixin. + */ + +/** Convert enum constant to entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template ::value > = 0> +flecs::entity to_entity(E constant) const; + + +# ifdef FLECS_MODULE +/** + * @file addons/cpp/mixins/module/mixin.inl + * @brief Module world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_addons_modules + * + * @{ + */ + +/** Define a module. + * This operation is not mandatory, but can be called inside the module ctor to + * obtain the entity associated with the module, or override the module name. + * + * @tparam Module module class. + * @return Module entity. + */ +template +flecs::entity module(const char *name = nullptr) const; + +/** Import a module. + * + * @tparam Module module class. + * @return Module entity. + */ +template +flecs::entity import(); + +/** @} */ + +# endif +# ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/mixin.inl + * @brief Pipeline world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_pipelines + * + * @{ + */ + +/** Create a new pipeline. + * + * @return A pipeline builder. + */ +flecs::pipeline_builder<> pipeline() const; + +/** Create a new pipeline. + * + * @tparam Pipeline Type associated with pipeline. + * @return A pipeline builder. + */ +template ::value > = 0> +flecs::pipeline_builder<> pipeline() const; + +/** Set pipeline. + * @see ecs_set_pipeline + */ +void set_pipeline(const flecs::entity pip) const; + +/** Set pipeline. + * @see ecs_set_pipeline + */ +template +void set_pipeline() const; + +/** Get pipeline. + * @see ecs_get_pipeline + */ +flecs::entity get_pipeline() const; + +/** Progress world one tick. + * @see ecs_progress + */ +bool progress(ecs_ftime_t delta_time = 0.0) const; + +/** Run pipeline. + * @see ecs_run_pipeline + */ +void run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time = 0.0) const; + +/** Run pipeline. + * @tparam Pipeline Type associated with pipeline. + * @see ecs_run_pipeline + */ +template ::value > = 0> +void run_pipeline(ecs_ftime_t delta_time = 0.0) const; + +/** Set timescale. + * @see ecs_set_time_scale + */ +void set_time_scale(ecs_ftime_t mul) const; + +/** Set target FPS. + * @see ecs_set_target_fps + */ +void set_target_fps(ecs_ftime_t target_fps) const; + +/** Reset simulation clock. + * @see ecs_reset_clock + */ +void reset_clock() const; + +/** Set number of threads. + * @see ecs_set_threads + */ +void set_threads(int32_t threads) const; + +/** Set number of threads. + * @see ecs_get_stage_count + */ +int32_t get_threads() const; + +/** Set number of task threads. + * @see ecs_set_task_threads + */ +void set_task_threads(int32_t task_threads) const; + +/** Returns true if task thread use has been requested. + * @see ecs_using_task_threads + */ +bool using_task_threads() const; + +/** @} */ + +# endif +# ifdef FLECS_SNAPSHOT +/** + * @file addons/cpp/mixins/snapshot/mixin.inl + * @brief Snapshot world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_addons_snapshots + * + * @{ + */ + +/** Create a snapshot. + */ +template +flecs::snapshot snapshot(Args &&... args) const; + +/** @} */ + +# endif +# ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/mixin.inl + * @brief System module world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_addons_systems + * + * @{ +*/ + +/** Upcast entity to a system. + * The provided entity must be a system. + * + * @param e The entity. + * @return A system object. + */ +flecs::system system(flecs::entity e) const; + +/** Create a new system. + * + * @tparam Components The components to match on. + * @tparam Args Arguments passed to the constructor of flecs::system_builder. + * @return System builder. + */ +template +flecs::system_builder system(Args &&... args) const; + +/** @} */ + +# endif +# ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/mixin.inl + * @brief Timer module mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_addons_timer + */ + +/** Find or register a singleton timer. */ +template +flecs::timer timer() const; + +/** Find or register a timer. */ +template +flecs::timer timer(Args &&... args) const; + +/** Enable randomization of initial time values for timers. + * @see ecs_randomize_timers + */ +void randomize_timers() const; + +# endif +# ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/mixin.inl + * @brief Rule world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_addons_rules + * + * @{ + */ + +/** Create a rule. + * @see ecs_rule_init + */ +template +flecs::rule rule(Args &&... args) const; + +/** Create a subrule. + * @see ecs_rule_init + */ +template +flecs::rule rule(flecs::rule_base& parent, Args &&... args) const; + +/** Create a rule builder. + * @see ecs_rule_init + */ +template +flecs::rule_builder rule_builder(Args &&... args) const; + +/** @} */ + +# endif +# ifdef FLECS_PLECS +/** + * @file addons/cpp/mixins/plecs/mixin.inl + * @brief Plecs world mixin. + */ + +/** + * @defgroup cpp_addons_plecs Plecs + * @ingroup cpp_addons + * Data definition format for loading entity data. + * + * @{ + */ + +/** Load plecs string. + * @see ecs_plecs_from_str + */ +int plecs_from_str(const char *name, const char *str) const { + return ecs_plecs_from_str(m_world, name, str); +} + +/** Load plecs from file. + * @see ecs_plecs_from_file + */ +int plecs_from_file(const char *filename) const { + return ecs_plecs_from_file(m_world, filename); +} + +/** @} */ + +# endif +# ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/world.inl + * @brief Meta world mixin. + */ + +/** + * @memberof flecs::world + * @ingroup cpp_addons_meta + * + * @{ + */ + +/** Convert value to string */ +flecs::string to_expr(flecs::entity_t tid, const void* value) { + char *expr = ecs_ptr_to_expr(m_world, tid, value); + return flecs::string(expr); +} + +/** Convert value to string */ +template +flecs::string to_expr(const T* value) { + flecs::entity_t tid = _::cpp_type::id(m_world); + return to_expr(tid, value); +} + +/** Return meta cursor to value */ +flecs::cursor cursor(flecs::entity_t tid, void *ptr) { + return flecs::cursor(m_world, tid, ptr); +} + +/** Return meta cursor to value */ +template +flecs::cursor cursor(void *ptr) { + flecs::entity_t tid = _::cpp_type::id(m_world); + return cursor(tid, ptr); +} + +/** Create primitive type */ +flecs::entity primitive(flecs::meta::primitive_kind_t kind); + +/** Create array type. */ +flecs::entity array(flecs::entity_t elem_id, int32_t array_count); + +/** Create array type. */ +template +flecs::entity array(int32_t array_count); + +/** Create vector type. */ +flecs::entity vector(flecs::entity_t elem_id); + +/** Create vector type. */ +template +flecs::entity vector(); + +/** @} */ + +# endif +# ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/world.inl + * @brief JSON world mixin. + */ + +/** Serialize untyped value to JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +flecs::string to_json(flecs::entity_t tid, const void* value) { + char *json = ecs_ptr_to_json(m_world, tid, value); + return flecs::string(json); +} + +/** Serialize value to JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +template +flecs::string to_json(const T* value) { + flecs::entity_t tid = _::cpp_type::id(m_world); + return to_json(tid, value); +} + +/** Serialize world to JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +flecs::string to_json() { + return flecs::string( ecs_world_to_json(m_world, nullptr) ); +} + +/** Deserialize value from JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +const char* from_json(flecs::entity_t tid, void* value, const char *json, flecs::from_json_desc_t *desc = nullptr) { + return ecs_ptr_from_json(m_world, tid, value, json, desc); +} + +/** Deserialize value from JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +template +const char* from_json(T* value, const char *json, flecs::from_json_desc_t *desc = nullptr) { + return ecs_ptr_from_json(m_world, _::cpp_type::id(m_world), + value, json, desc); +} + +/** Deserialize JSON into world. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +const char* from_json(const char *json, flecs::from_json_desc_t *desc = nullptr) { + return ecs_world_from_json(m_world, json, desc); +} + +# endif +# ifdef FLECS_APP +/** + * @file addons/cpp/mixins/app/mixin.inl + * @brief App world addon mixin. + */ + +/** + * @ingroup cpp_addons_app + * @memberof flecs::world + * + * @{ + */ + +/** Return app builder. + * The app builder is a convenience wrapper around a loop that runs + * world::progress. An app allows for writing platform agnostic code, + * as it provides hooks to modules for overtaking the main loop which is + * required for frameworks like emscripten. + */ +flecs::app_builder app() { + m_owned = false; // App takes ownership of world + return flecs::app_builder(m_world); +} + +/** @} */ + +# endif +# ifdef FLECS_METRICS + +/** Create metric. + * + * @ingroup cpp_addons_metrics + * @memberof flecs::world + */ +template +flecs::metric_builder metric(Args &&... args) const; + +# endif +# ifdef FLECS_ALERTS + +/** Create alert. + * + * @ingroup cpp_addons_alerts + * @memberof flecs::world + */ +template +flecs::alert_builder alert(Args &&... args) const; + +# endif + +public: + void init_builtin_components(); + + world_t *m_world; + bool m_owned; +}; + +/** Scoped world. + * Utility class used by the world::scope method to create entities in a scope. + */ +struct scoped_world : world { + scoped_world( + flecs::world_t *w, + flecs::entity_t s) : world(nullptr) + { + m_prev_scope = ecs_set_scope(w, s); + m_world = w; + m_owned = false; + } + + ~scoped_world() { + ecs_set_scope(m_world, m_prev_scope); + } + + scoped_world(const scoped_world& obj) : world(nullptr) { + m_prev_scope = obj.m_prev_scope; + m_world = obj.m_world; + m_owned = obj.m_owned; + } + + flecs::entity_t m_prev_scope; +}; + +/** @} */ + +} // namespace flecs + +/** + * @file addons/cpp/iter.hpp + * @brief Wrapper classes for ecs_iter_t and component arrays. + */ + +#pragma once + +/** + * @defgroup cpp_iterator Iterators + * @ingroup cpp_core + * Iterator operations. + * + * @{ + */ + +namespace flecs +{ + +/** 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. + * + * @ingroup cpp_iterator + */ +struct untyped_column { + untyped_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) const { + 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); + } + +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. + * + * @ingroup cpp_iterator + */ +template +struct column { + static_assert(std::is_empty::value == false, + "invalid type for column, cannot iterate empty type"); + + /** 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) const { + 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*() const { + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return *m_array; + } + + /** Return first element of component array. + * This operator is typically used when the column is shared. + * + * @return Pointer to the first element. + */ + T* operator->() const { + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return m_array; + } + +protected: + T* m_array; + size_t m_count; + bool m_is_shared; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +namespace _ { + +//////////////////////////////////////////////////////////////////////////////// + +/** Iterate over an integer range (used to iterate over entity range). + * + * @tparam T of the iterator + */ +template +struct range_iterator +{ + 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 _ + +} // namespace flecs + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// + +/** Class for iterating over query results. + * + * @ingroup cpp_iterator + */ +struct iter { +private: + 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(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); + } + + flecs::entity system() const; + + flecs::entity event() const; + + flecs::id event_id() const; + + flecs::world world() const; + + const flecs::iter_t* c_ptr() const { + return m_iter; + } + + size_t count() const { + return static_cast(m_iter->count); + } + + ecs_ftime_t delta_time() const { + return m_iter->delta_time; + } + + ecs_ftime_t delta_system_time() const { + return m_iter->delta_system_time; + } + + flecs::type type() const; + + flecs::table table() const; + + flecs::table_range range() const; + + /** Access ctx. + * ctx contains the context pointer assigned to a system. + */ + void* ctx() { + return m_iter->ctx; + } + + /** Access ctx. + * ctx contains the context pointer assigned to a system. + */ + template + T* ctx() { + return static_cast(m_iter->ctx); + } + + /** Access param. + * param contains the pointer passed to the param argument of system::run + */ + void* param() { + return m_iter->param; + } + + /** Access param. + * param contains the pointer passed to the param argument of system::run + */ + template + T* param() { + /* TODO: type check */ + return static_cast(m_iter->param); + } + + /** Obtain mutable handle to entity being iterated over. + * + * @param row Row being iterated over. + */ + flecs::entity entity(size_t row) const; + + /** Returns whether field is matched on self. + * + * @param index The field index. + */ + bool is_self(int32_t index) const { + return ecs_field_is_self(m_iter, index); + } + + /** Returns whether field is set. + * + * @param index The field index. + */ + bool is_set(int32_t index) const { + return ecs_field_is_set(m_iter, index); + } + + /** Returns whether field is readonly. + * + * @param index The field index. + */ + bool is_readonly(int32_t index) const { + return ecs_field_is_readonly(m_iter, index); + } + + /** Number of fields in iterator. + */ + int32_t field_count() const { + return m_iter->field_count; + } + + /** Size of field data type. + * + * @param index The field id. + */ + size_t size(int32_t index) const { + return ecs_field_size(m_iter, index); + } + + /** Obtain field source (0 if This). + * + * @param index The field index. + */ + flecs::entity src(int32_t index) const; + + /** Obtain id matched for field. + * + * @param index The field index. + */ + flecs::id id(int32_t index) const; + + /** Obtain pair id matched for field. + * This operation will fail if the id is not a pair. + * + * @param index The field index. + */ + flecs::id pair(int32_t index) const; + + /** Obtain column index for field. + * + * @param index The field index. + */ + int32_t column_index(int32_t index) const { + return ecs_field_column_index(m_iter, index); + } + + /** Convert current iterator result to string. + */ + flecs::string str() const { + char *s = ecs_iter_str(m_iter); + return flecs::string(s); + } + + /** Get readonly access to field data. + * If the specified field index does not match with the provided type, the + * function will assert. + * + * @tparam T Type of the field. + * @param index The field index. + * @return The field data. + */ + template , + typename std::enable_if::value, void>::type* = nullptr> + flecs::column field(int32_t index) const { + return get_field(index); + } + + /** Get read/write access to field data. + * If the matched id for the specified field does not match with the provided + * type or if the field is readonly, the function will assert. + * + * @tparam T Type of the field. + * @param index The field index. + * @return The field data. + */ + template , + typename std::enable_if< + std::is_const::value == false, void>::type* = nullptr> + flecs::column field(int32_t index) const { + ecs_assert(!ecs_field_is_readonly(m_iter, index), + ECS_ACCESS_VIOLATION, NULL); + return get_field(index); + } + + /** Get unchecked access to field data. + * Unchecked access is required when a system does not know the type of a + * field at compile time. + * + * @param index The field index. + */ + flecs::untyped_column field(int32_t index) const { + return get_unchecked_field(index); + } + + /** Get readonly access to entity ids. + * + * @return The entity ids. + */ + flecs::column entities() const { + return flecs::column(m_iter->entities, static_cast(m_iter->count), false); + } + + /** Obtain the total number of tables the iterator will iterate over. */ + int32_t table_count() const { + return m_iter->table_count; + } + + /** Check if the current table has changed since the last iteration. + * Can only be used when iterating queries and/or systems. */ + bool changed() { + return ecs_query_changed(nullptr, m_iter); + } + + /** Skip current table. + * This indicates to the query that the data in the current table is not + * modified. By default, iterating a table with a query will mark the + * iterated components as dirty if they are annotated with InOut or Out. + * + * When this operation is invoked, the components of the current table will + * not be marked dirty. */ + void skip() { + ecs_query_skip(m_iter); + } + + /* Return group id for current table (grouped queries only) */ + uint64_t group_id() const { + return m_iter->group_id; + } + +#ifdef FLECS_RULES + /** Get value of variable by id. + * Get value of a query variable for current result. + */ + flecs::entity get_var(int var_id) const; + + /** Get value of variable by name. + * Get value of a query variable for current result. + */ + flecs::entity get_var(const char *name) const; +#endif + +private: + /* Get field, check if correct type is used */ + template > + flecs::column get_field(int32_t index) const { + +#ifndef FLECS_NDEBUG + ecs_entity_t term_id = ecs_field_id(m_iter, index); + ecs_assert(ECS_HAS_ID_FLAG(term_id, PAIR) || + term_id == _::cpp_type::id(m_iter->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif + + size_t count; + bool is_shared = !ecs_field_is_self(m_iter, index); + + /* 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_field_w_size(m_iter, sizeof(A), index)), + count, is_shared); + } + + flecs::untyped_column get_unchecked_field(int32_t index) const { + size_t count; + size_t size = ecs_field_size(m_iter, index); + bool is_shared = !ecs_field_is_self(m_iter, index); + + /* 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::untyped_column( + ecs_field_w_size(m_iter, 0, index), size, count, is_shared); + } + + flecs::iter_t *m_iter; + std::size_t m_begin; + std::size_t m_end; +}; + +} // namespace flecs + +/** @} */ + +/** + * @file addons/cpp/entity.hpp + * @brief Entity class. + * + * This class provides read/write access to entities. + */ + +#pragma once + +/** + * @file addons/cpp/entity_view.hpp + * @brief Entity class with only readonly operations. + * + * This class provides readonly access to entities. Using this class to store + * entities in components ensures valid handles, as this class will always store + * the actual world vs. a stage. The constructors of this class will never + * create a new entity. + * + * To obtain a mutable handle to the entity, use the "mut" function. + */ + +#pragma once + +/** + * @ingroup cpp_entities + * @{ + */ + +namespace flecs +{ + +/** Entity view. + * Class with read operations for entities. Base for flecs::entity. + * + * @ingroup cpp_entities + */ +struct entity_view : public id { + + entity_view() : flecs::id() { } + + /** Wrap an existing entity id. + * + * @param world The world in which the entity is created. + * @param id The entity id. + */ + explicit entity_view(flecs::world_t *world, flecs::id_t id) + : flecs::id(world + ? const_cast(ecs_get_world(world)) + : nullptr + , id ) { } + + /** Implicit conversion from flecs::entity_t to flecs::entity_view. */ + entity_view(entity_t id) + : flecs::id( nullptr, id ) { } + + /** Get entity id. + * @return The integer entity id. + */ + entity_t id() const { + return m_id; + } + + /** Check if entity is valid. + * + * @return True if the entity is alive, false otherwise. + */ + bool is_valid() const { + return m_world && ecs_is_valid(m_world, m_id); + } + + explicit operator bool() const { + return is_valid(); + } + + /** Check if entity is alive. + * + * @return True if the entity is alive, false otherwise. + */ + bool is_alive() const { + return m_world && ecs_is_alive(m_world, m_id); + } + + /** Return the entity name. + * + * @return The entity name. + */ + flecs::string_view name() const { + return flecs::string_view(ecs_get_name(m_world, m_id)); + } + + /** Return the entity symbol. + * + * @return The entity symbol. + */ + flecs::string_view symbol() const { + return flecs::string_view(ecs_get_symbol(m_world, m_id)); + } + + /** Return the entity path. + * + * @return The hierarchical entity path. + */ + flecs::string path(const char *sep = "::", const char *init_sep = "::") const { + return path_from(0, sep, init_sep); + } + + /** Return the entity path relative to a parent. + * + * @return The relative hierarchical entity path. + */ + flecs::string path_from(flecs::entity_t parent, const char *sep = "::", const char *init_sep = "::") const { + char *path = ecs_get_path_w_sep(m_world, parent, m_id, sep, init_sep); + return flecs::string(path); + } + + /** Return the entity path relative to a parent. + * + * @return The relative hierarchical entity path. + */ + template + flecs::string path_from(const char *sep = "::", const char *init_sep = "::") const { + return path_from(_::cpp_type::id(m_world), sep, init_sep); + } + + bool enabled() const { + return !ecs_has_id(m_world, m_id, flecs::Disabled); + } + + /** Get the entity's type. + * + * @return The entity's type. + */ + flecs::type type() const; + + /** Get the entity's table. + * + * @return Returns the entity's table. + */ + flecs::table table() const; + + /** Get table range for the entity. + * Returns a range with the entity's row as offset and count set to 1. If + * the entity is not stored in a table, the function returns a range with + * count 0. + * + * @return Returns the entity's table range. + */ + flecs::table_range range() const; + + /** Iterate (component) ids of an entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::id id) + * @endcode + * + * @param func The function invoked for each id. + */ + template + void each(const Func& func) const; + + /** Iterate matching pair ids of an entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::id id) + * @endcode + * + * @param func The function invoked for each id. + */ + template + void each(flecs::id_t first, flecs::id_t second, const Func& func) const; + + /** Iterate targets for a given relationship. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @param rel The relationship for which to iterate the targets. + * @param func The function invoked for each target. + */ + template + void each(const flecs::entity_view& rel, const Func& func) const; + + /** Iterate targets for a given relationship. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @tparam First The relationship for which to iterate the targets. + * @param func The function invoked for each target. + */ + template + void each(const Func& func) const { + return each(_::cpp_type::id(m_world), func); + } + + /** Iterate children for entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @param rel The relationship to follow. + * @param func The function invoked for each child. + */ + template + void children(flecs::entity_t rel, Func&& func) const { + /* When the entity is a wildcard, this would attempt to query for all + * entities with (ChildOf, *) or (ChildOf, _) instead of querying for + * the children of the wildcard entity. */ + if (m_id == flecs::Wildcard || m_id == flecs::Any) { + /* This is correct, wildcard entities don't have children */ + return; + } + + flecs::world world(m_world); + + ecs_term_t terms[2]; + ecs_filter_t f = ECS_FILTER_INIT; + f.terms = terms; + f.term_count = 2; + + ecs_filter_desc_t desc = {}; + desc.terms[0].first.id = rel; + desc.terms[0].second.id = m_id; + desc.terms[0].second.flags = EcsIsEntity; + desc.terms[1].id = flecs::Prefab; + desc.terms[1].oper = EcsOptional; + desc.storage = &f; + if (ecs_filter_init(m_world, &desc) != nullptr) { + ecs_iter_t it = ecs_filter_iter(m_world, &f); + while (ecs_filter_next(&it)) { + _::each_delegate(FLECS_MOV(func)).invoke(&it); + } + + ecs_filter_fini(&f); + } + } + + /** Iterate children for entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @tparam Rel The relationship to follow. + * @param func The function invoked for each child. + */ + template + void children(Func&& func) const { + children(_::cpp_type::id(m_world), FLECS_MOV(func)); + } + + /** Iterate children for entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * This operation follows the ChildOf relationship. + * + * @param func The function invoked for each child. + */ + template + void children(Func&& func) const { + children(flecs::ChildOf, FLECS_MOV(func)); + } + + /** 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 ::value > = 0> + const T* get() const { + auto comp_id = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast(ecs_get_id(m_world, m_id, comp_id)); + } + + /** Get component value. + * Overload for when T is not the same as the actual type, which happens + * when using pair types. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template , + if_t< flecs::is_pair::value > = 0> + const A* get() const { + auto comp_id = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast(ecs_get_id(m_world, m_id, comp_id)); + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value > = 0> + const A* get() const { + return this->get

(); + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template::value> = 0> + const First* get(Second second) const { + auto comp_id = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_id(m_world, m_id, ecs_pair(comp_id, second))); + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param constant the enum constant. + */ + template::value> = 0> + const First* get(Second constant) const { + const auto& et = enum_type(this->m_world); + flecs::entity_t target = et.entity(constant); + return get(target); + } + + /** Get component value (untyped). + * + * @param comp The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + const void* get(flecs::id_t comp) const { + return ecs_get_id(m_world, m_id, comp); + } + + /** Get a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * first nor the second part of the pair are components, the operation + * will fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + const void* get(flecs::entity_t first, flecs::entity_t second) const { + return ecs_get_id(m_world, m_id, ecs_pair(first, second)); + } + + /** Get 1..N components. + * This operation accepts a callback with as arguments the components to + * retrieve. The callback will only be invoked when the entity has all + * the components. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. + * + * While the callback is invoked the table in which the components are + * stored is locked, which prevents mutations that could cause invalidation + * of the component references. Note that this is not an actual lock: + * invalid access causes a runtime panic and so it is still up to the + * application to ensure access is protected. + * + * The component arguments must be references and can be either const or + * non-const. When all arguments are const, the function will read-lock the + * table (see ecs_read_begin). If one or more arguments are non-const the + * function will write-lock the table (see ecs_write_begin). + * + * Example: + * + * @code + * e.get([](Position& p, Velocity& v) { // write lock + * p.x += v.x; + * }); + * + * e.get([](const Position& p) { // read lock + * std::cout << p.x << std::endl; + * }); + * @endcode + * + * @param func The callback to invoke. + * @return True if the entity has all components, false if not. + */ + template ::value > = 0> + bool get(const Func& func) const; + + /** Get enum constant. + * + * @tparam T The enum type for which to get the constant + * @return Constant entity if found, 0 entity if not. + */ + template ::value > = 0> + const T* get() const; + + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam Second the second element of a pair. + * @param first The first part of the pair. + */ + template + const Second* get_second(flecs::entity_t first) const { + auto second = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_id(m_world, m_id, ecs_pair(first, second))); + } + + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template + const Second* get_second() const { + return get>(); + } + + /** Get target for a given pair. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @tparam First The first element of the pair. + * @param index The index (0 for the first instance of the relationship). + */ + template + flecs::entity target(int32_t index = 0) const; + + /** Get target for a given pair. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @param first The first element of the pair for which to retrieve the target. + * @param index The index (0 for the first instance of the relationship). + */ + flecs::entity target(flecs::entity_t first, int32_t index = 0) const; + + /** Get the target of a pair for a given relationship id. + * This operation returns the first entity that has the provided id by following + * the specified relationship. If the entity itself has the id then entity will + * be returned. If the id cannot be found on the entity or by following the + * relationship, the operation will return 0. + * + * This operation can be used to lookup, for example, which prefab is providing + * a component by specifying the IsA pair: + * + * @code + * // Is Position provided by the entity or one of its base entities? + * ecs_get_target_for_id(world, entity, EcsIsA, ecs_id(Position)) + * @endcode + * + * @param relationship The relationship to follow. + * @param id The id to lookup. + * @return The entity for which the target has been found. + */ + flecs::entity target_for(flecs::entity_t relationship, flecs::id_t id) const; + + template + flecs::entity target_for(flecs::entity_t relationship) const; + + template + flecs::entity target_for(flecs::entity_t relationship) const; + + /** Get depth for given relationship. + * + * @param rel The relationship. + * @return The depth. + */ + int32_t depth(flecs::entity_t rel) const { + return ecs_get_depth(m_world, m_id, rel); + } + + /** Get depth for given relationship. + * + * @tparam Rel The relationship. + * @return The depth. + */ + template + int32_t depth() const { + return this->depth(_::cpp_type::id(m_world)); + } + + /** Get parent of entity. + * Short for target(flecs::ChildOf). + * + * @return The parent of the entity. + */ + flecs::entity parent() const; + + /** 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. + * @param search_path When false, only the entity's scope is searched. + * @return The found entity, or entity::null if no entity matched. + */ + flecs::entity lookup(const char *path, bool search_path = false) const; + + /** Check if entity has the provided entity. + * + * @param e The entity to check. + * @return True if the entity has the provided entity, false otherwise. + */ + bool has(flecs::id_t e) const { + return ecs_has_id(m_world, m_id, e); + } + + /** 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 { + flecs::id_t cid = _::cpp_type::id(m_world); + bool result = ecs_has_id(m_world, m_id, cid); + if (result) { + return result; + } + + if (is_enum::value) { + return ecs_has_pair(m_world, m_id, cid, flecs::Wildcard); + } + + return false; + } + + /** Check if entity has the provided enum constant. + * + * @tparam E The enum type (can be deduced). + * @param value The enum constant to check. + * @return True if the entity has the provided constant, false otherwise. + */ + template ::value > = 0> + bool has(E value) const { + auto r = _::cpp_type::id(m_world); + auto o = enum_type(m_world).entity(value); + return ecs_has_pair(m_world, m_id, r, o); + } + + /** Check if entity has the provided pair. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return True if the entity has the provided component, false otherwise. + */ + template + bool has() const { + return this->has(_::cpp_type::id(m_world)); + } + + /** Check if entity has the provided pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity has the provided component, false otherwise. + */ + template::value > = 0> + bool has(Second second) const { + auto comp_id = _::cpp_type::id(m_world); + return ecs_has_id(m_world, m_id, ecs_pair(comp_id, second)); + } + + /** Check if entity has the provided pair. + * + * @tparam Second The second element of the pair. + * @param first The first element of the pair. + * @return True if the entity has the provided component, false otherwise. + */ + template + bool has_second(flecs::entity_t first) const { + return this->has(first, _::cpp_type::id(m_world)); + } + + /** Check if entity has the provided pair. + * + * @tparam First The first element of the pair. + * @param value The enum constant. + * @return True if the entity has the provided component, false otherwise. + */ + template::value > = 0> + bool has(E value) const { + const auto& et = enum_type(this->m_world); + flecs::entity_t second = et.entity(value); + return has(second); + } + + /** Check if entity has the provided pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity has the provided component, false otherwise. + */ + bool has(flecs::id_t first, flecs::id_t second) const { + return ecs_has_id(m_world, m_id, ecs_pair(first, second)); + } + + /** Check if entity owns the provided entity. + * An entity is owned if it is not shared from a base entity. + * + * @param e The entity to check. + * @return True if the entity owns the provided entity, false otherwise. + */ + bool owns(flecs::id_t e) const { + return ecs_owns_id(m_world, m_id, e); + } + + /** Check if entity owns the provided pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity owns the provided component, false otherwise. + */ + template + bool owns(flecs::id_t second) const { + auto comp_id = _::cpp_type::id(m_world); + return owns(ecs_pair(comp_id, second)); + } + + /** Check if entity owns the provided pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity owns the provided component, false otherwise. + */ + bool owns(flecs::id_t first, flecs::id_t second) const { + return owns(ecs_pair(first, second)); + } + + /** 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(_::cpp_type::id(m_world)); + } + + /** Check if entity owns the provided pair. + * An pair is owned if it is not shared from a base entity. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return True if the entity owns the provided pair, false otherwise. + */ + template + bool owns() const { + return owns( + _::cpp_type::id(m_world), + _::cpp_type::id(m_world)); + } + + /** Test if id is enabled. + * + * @param id The id to test. + * @return True if enabled, false if not. + */ + bool enabled(flecs::id_t id) const { + return ecs_is_enabled_id(m_world, m_id, id); + } + + /** Test if component is enabled. + * + * @tparam T The component to test. + * @return True if enabled, false if not. + */ + template + bool enabled() const { + return this->enabled(_::cpp_type::id(m_world)); + } + + /** Test if pair is enabled. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return True if enabled, false if not. + */ + bool enabled(flecs::id_t first, flecs::id_t second) const { + return this->enabled(ecs_pair(first, second)); + } + + /** Test if pair is enabled. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return True if enabled, false if not. + */ + template + bool enabled(flecs::id_t second) const { + return this->enabled(_::cpp_type::id(m_world), second); + } + + /** Test if pair is enabled. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return True if enabled, false if not. + */ + template + bool enabled() const { + return this->enabled(_::cpp_type::id(m_world)); + } + + flecs::entity clone(bool clone_value = true, flecs::entity_t dst_id = 0) const; + + /** Return mutable entity handle for current stage + * When an entity handle created from the world is used while the world is + * in staged mode, it will only allow for readonly operations since + * structural changes are not allowed on the world while in staged mode. + * + * To do mutations on the entity, this operation provides a handle to the + * entity that uses the stage instead of the actual world. + * + * Note that staged entity handles should never be stored persistently, in + * components or elsewhere. An entity handle should always point to the + * main world. + * + * Also note that this operation is not necessary when doing mutations on an + * entity outside of a system. It is allowed to do entity operations + * directly on the world, as long as the world is not in staged mode. + * + * @param stage The current stage. + * @return An entity handle that allows for mutations in the current stage. + */ + flecs::entity mut(const flecs::world& stage) const; + + /** Same as mut(world), but for iterator. + * This operation allows for the construction of a mutable entity handle + * from an iterator. + * + * @param it An iterator that contains a reference to the world or stage. + * @return An entity handle that allows for mutations in the current stage. + */ + flecs::entity mut(const flecs::iter& it) const; + + /** Same as mut(world), but for entity. + * This operation allows for the construction of a mutable entity handle + * from another entity. This is useful in each() functions, which only + * provide a handle to the entity being iterated over. + * + * @param e Another mutable entity. + * @return An entity handle that allows for mutations in the current stage. + */ + flecs::entity mut(const flecs::entity_view& e) const; + +# ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/entity_view.inl + * @brief JSON entity mixin. + */ + +/** Serialize entity to JSON. + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_json + */ +flecs::string to_json(const flecs::entity_to_json_desc_t *desc = nullptr) const { + char *json = ecs_entity_to_json(m_world, m_id, desc); + return flecs::string(json); +} + +# endif +# ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/entity_view.inl + * @brief Doc entity view mixin. + */ + +/** Get human readable name. + * + * @see ecs_doc_get_name() + * @see flecs::doc::get_name() + * @see flecs::entity_builder::set_doc_name() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_name() const { + return ecs_doc_get_name(m_world, m_id); +} + +/** Get brief description. + * + * @see ecs_doc_get_brief() + * @see flecs::doc::get_brief() + * @see flecs::entity_builder::set_doc_brief() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_brief() const { + return ecs_doc_get_brief(m_world, m_id); +} + +/** Get detailed description. + * + * @see ecs_doc_get_detail() + * @see flecs::doc::get_detail() + * @see flecs::entity_builder::set_doc_detail() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_detail() const { + return ecs_doc_get_detail(m_world, m_id); +} + +/** Get link to external documentation. + * + * @see ecs_doc_get_link() + * @see flecs::doc::get_link() + * @see flecs::entity_builder::set_doc_link() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_link() const { + return ecs_doc_get_link(m_world, m_id); +} + +/** Get color. + * + * @see ecs_doc_get_color() + * @see flecs::doc::get_color() + * @see flecs::entity_builder::set_doc_color() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_color() const { + return ecs_doc_get_color(m_world, m_id); +} + +# endif +# ifdef FLECS_ALERTS +/** + * @file addons/cpp/mixins/alerts/entity_view.inl + * @brief Alerts entity mixin. + */ + +/** Return number of alerts for entity. + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_alerts + */ +int32_t alert_count(flecs::entity_t alert = 0) const { + return ecs_get_alert_count(m_world, m_id, alert); +} + +# endif + +/** + * @file addons/cpp/mixins/enum/entity_view.inl + * @brief Enum entity view mixin. + */ + +/** Convert entity to enum constant. + * + * @memberof flecs::entity_view + * @ingroup cpp_entities + */ +template +E to_constant() const; + + +/** + * @file addons/cpp/mixins/event/entity_view.inl + * @brief Event entity mixin. + */ + +/** Emit event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to emit. + */ +void emit(flecs::entity_t evt) { + flecs::world(m_world) + .event(evt) + .entity(m_id) + .emit(); +} + +/** Emit event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to emit. + */ +void emit(flecs::entity evt); + +/** Emit event for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to emit. + */ +template ::value> = 0> +void emit() { + this->emit(_::cpp_type::id(m_world)); +} + +/** Emit event with payload for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to emit. + */ +template ::value> = 0> +void emit(const Evt& payload) { + flecs::world(m_world) + .event(_::cpp_type::id(m_world)) + .entity(m_id) + .ctx(&payload) + .emit(); +} + + +/** Enqueue event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to enqueue. + */ +void enqueue(flecs::entity_t evt) { + flecs::world(m_world) + .event(evt) + .entity(m_id) + .enqueue(); +} + +/** Enqueue event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to enqueue. + */ +void enqueue(flecs::entity evt); + +/** Enqueue event for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to enqueue. + */ +template ::value> = 0> +void enqueue() { + this->enqueue(_::cpp_type::id(m_world)); +} + +/** Enqueue event with payload for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to enqueue. + */ +template ::value> = 0> +void enqueue(const Evt& payload) { + flecs::world(m_world) + .event(_::cpp_type::id(m_world)) + .entity(m_id) + .ctx(&payload) + .enqueue(); +} + + +private: + flecs::entity set_stage(world_t *stage); +}; + +} + +/** @} */ + +/** + * @file addons/cpp/mixins/entity/builder.hpp + * @brief Entity builder. + */ + +#pragma once + +namespace flecs +{ + +/** Entity builder. + * @ingroup cpp_entities + */ +template +struct entity_builder : entity_view { + + using entity_view::entity_view; + + /** 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 + Self& add() { + flecs_static_assert(is_flecs_constructible::value, + "cannot default construct type: add T::T() or use emplace()"); + ecs_add_id(this->m_world, this->m_id, _::cpp_type::id(this->m_world)); + return to_base(); + } + + /** Add pair for enum constant. + * This operation will add a pair to the entity where the first element is + * the enumeration type, and the second element the enumeration constant. + * + * The operation may be used with regular (C style) enumerations as well as + * enum classes. + * + * @param value The enumeration value. + */ + template ::value > = 0> + Self& add(E value) { + flecs::entity_t first = _::cpp_type::id(this->m_world); + const auto& et = enum_type(this->m_world); + flecs::entity_t second = et.entity(value); + return this->add(first, second); + } + + /** Add an entity to an entity. + * Add an entity to the entity. This is typically used for tagging. + * + * @param component The component to add. + */ + Self& add(id_t component) { + ecs_add_id(this->m_world, this->m_id, component); + return to_base(); + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + Self& add(entity_t first, entity_t second) { + ecs_add_pair(this->m_world, this->m_id, first, second); + return to_base(); + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + */ + template + Self& add() { + return this->add(_::cpp_type::id(this->m_world)); + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + */ + template::value > = 0> + Self& add(Second second) { + flecs_static_assert(is_flecs_constructible::value, + "cannot default construct type: add T::T() or use emplace()"); + return this->add(_::cpp_type::id(this->m_world), second); + } + + /** Add a pair. + * This operation adds a pair to the entity that consists out of a tag + * combined with an enum constant. + * + * @tparam First The first element of the pair + * @param constant the enum constant. + */ + template::value > = 0> + Self& add(Second constant) { + flecs_static_assert(is_flecs_constructible::value, + "cannot default construct type: add T::T() or use emplace()"); + const auto& et = enum_type(this->m_world); + return this->add(et.entity(constant)); + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @param first The first element of the pair + * @tparam Second The second element of the pair + */ + template + Self& add_second(flecs::entity_t first) { + return this->add(first, _::cpp_type::id(this->m_world)); + } + + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @param cond The condition to evaluate. + * @param component The component to add. + */ + Self& add_if(bool cond, flecs::id_t component) { + if (cond) { + return this->add(component); + } else { + return this->remove(component); + } + } + + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @tparam T The component to add. + * @param cond The condition to evaluate. + */ + template + Self& add_if(bool cond) { + if (cond) { + return this->add(); + } else { + return this->remove(); + } + } + + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @param cond The condition to evaluate. + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + Self& add_if(bool cond, flecs::entity_t first, flecs::entity_t second) { + if (cond) { + return this->add(first, second); + } else { + /* If second is 0 or if relationship is exclusive, use wildcard for + * second which will remove all instances of the relationship. + * Replacing 0 with Wildcard will make it possible to use the second + * as the condition. */ + if (!second || ecs_has_id(this->m_world, first, flecs::Exclusive)) { + second = flecs::Wildcard; + } + return this->remove(first, second); + } + } + + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @tparam First The first element of the pair + * @param cond The condition to evaluate. + * @param second The second element of the pair. + */ + template + Self& add_if(bool cond, flecs::entity_t second) { + return this->add_if(cond, _::cpp_type::id(this->m_world), second); + } + + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + * @param cond The condition to evaluate. + */ + template + Self& add_if(bool cond) { + return this->add_if(cond, _::cpp_type::id(this->m_world)); + } + + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @param cond The condition to evaluate. + * @param constant The enumeration constant. + */ + template ::value > = 0> + Self& add_if(bool cond, E constant) { + const auto& et = enum_type(this->m_world); + return this->add_if(cond, et.entity(constant)); + } + + /** Shortcut for add(IsA, entity). + * + * @param second The second element of the pair. + */ + Self& is_a(entity_t second) { + return this->add(flecs::IsA, second); + } + + /** Shortcut for add(IsA, entity). + * + * @tparam T the type associated with the entity. + */ + template + Self& is_a() { + return this->add(flecs::IsA, _::cpp_type::id(this->m_world)); + } + + /** Shortcut for add(ChildOf, entity). + * + * @param second The second element of the pair. + */ + Self& child_of(entity_t second) { + return this->add(flecs::ChildOf, second); + } + + /** Shortcut for add(DependsOn, entity). + * + * @param second The second element of the pair. + */ + Self& depends_on(entity_t second) { + return this->add(flecs::DependsOn, second); + } + + /** Shortcut for add(SlotOf, entity). + * + * @param second The second element of the pair. + */ + Self& slot_of(entity_t second) { + return this->add(flecs::SlotOf, second); + } + + /** Shortcut for add(SlotOf, target(ChildOf)). + */ + Self& slot() { + ecs_check(ecs_get_target(m_world, m_id, flecs::ChildOf, 0), + ECS_INVALID_PARAMETER, "add ChildOf pair before using slot()"); + return this->slot_of(this->target(flecs::ChildOf)); + error: + return to_base(); + } + + /** Shortcut for add(ChildOf, entity). + * + * @tparam T the type associated with the entity. + */ + template + Self& child_of() { + return this->child_of(_::cpp_type::id(this->m_world)); + } + + /** Shortcut for add(DependsOn, entity). + * + * @tparam T the type associated with the entity. + */ + template + Self& depends_on() { + return this->depends_on(_::cpp_type::id(this->m_world)); + } + + /** Shortcut for add(SlotOf, entity). + * + * @tparam T the type associated with the entity. + */ + template + Self& slot_of() { + return this->slot_of(_::cpp_type::id(this->m_world)); + } + + /** Remove a component from an entity. + * + * @tparam T the type of the component to remove. + */ + template ::value > = 0> + Self& remove() { + ecs_remove_id(this->m_world, this->m_id, _::cpp_type::id(this->m_world)); + return to_base(); + } + + /** Remove pair for enum. + * This operation will remove any (Enum, *) pair from the entity. + * + * @tparam E The enumeration type. + */ + template ::value > = 0> + Self& remove() { + flecs::entity_t first = _::cpp_type::id(this->m_world); + return this->remove(first, flecs::Wildcard); + } + + /** Remove an entity from an entity. + * + * @param entity The entity to remove. + */ + Self& remove(entity_t entity) { + ecs_remove_id(this->m_world, this->m_id, entity); + return to_base(); + } + + /** Remove a pair. + * This operation removes a pair from the entity. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + Self& remove(entity_t first, entity_t second) { + ecs_remove_pair(this->m_world, this->m_id, first, second); + return to_base(); + } + + /** Removes a pair. + * This operation removes a pair from the entity. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + */ + template + Self& remove() { + return this->remove(_::cpp_type::id(this->m_world)); + } + + /** Remove a pair. + * This operation removes the pair from the entity. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + */ + template::value > = 0> + Self& remove(Second second) { + return this->remove(_::cpp_type::id(this->m_world), second); + } + + /** Removes a pair. + * This operation removes a pair from the entity. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair + */ + template + Self& remove_second(flecs::entity_t first) { + return this->remove(first, _::cpp_type::id(this->m_world)); + } + + /** Remove a pair. + * This operation removes the pair from the entity. + * + * @tparam First The first element of the pair + * @param constant the enum constant. + */ + template::value > = 0> + Self& remove(Second constant) { + const auto& et = enum_type(this->m_world); + flecs::entity_t second = et.entity(constant); + return this->remove(second); + } + + /** Mark id for auto-overriding. + * When an entity inherits from a base entity (using the IsA relationship) + * any ids marked for auto-overriding on the base will be overridden + * automatically by the entity. + * + * @param id The id to mark for overriding. + */ + Self& override(flecs::id_t id) { + return this->add(ECS_OVERRIDE | id); + } + + /** Mark pair for auto-overriding. + * @see override(flecs::id_t id) + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + Self& override(flecs::entity_t first, flecs::entity_t second) { + return this->override(ecs_pair(first, second)); + } + + /** Mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam T The component to mark for overriding. + */ + template + Self& override() { + return this->override(_::cpp_type::id(this->m_world)); + } + + /** Mark pair for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + Self& override(flecs::entity_t second) { + return this->override(_::cpp_type::id(this->m_world), second); + } + + /** Mark pair for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template + Self& override() { + return this->override(_::cpp_type::id(this->m_world)); + } + + /** Set component, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam T The component to set and for which to add the OVERRIDE flag + */ + template + Self& set_override(const T& val) { + this->override(); + return this->set(val); + } + + /** Set component, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam T The component to set and for which to add the OVERRIDE flag + */ + template + Self& set_override(T&& val) { + this->override(); + return this->set(FLECS_FWD(val)); + } + + /** Set pair, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + Self& set_override(flecs::entity_t second, const First& val) { + this->override(second); + return this->set(second, val); + } + + /** Set pair, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + Self& set_override(flecs::entity_t second, First&& val) { + this->override(second); + return this->set(second, FLECS_FWD(val)); + } + + /** Set component, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& set_override(const A& val) { + this->override(); + return this->set(val); + } + + /** Set component, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& set_override(A&& val) { + this->override(); + return this->set(FLECS_FWD(val)); + } + + /** Emplace component, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam T The component to emplace and override. + */ + template + Self& emplace_override(Args&&... args) { + this->override(); + + flecs::emplace(this->m_world, this->m_id, + _::cpp_type::id(this->m_world), FLECS_FWD(args)...); + + return to_base(); + } + + /** Emplace pair, mark pair for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair to emplace and override. + * @tparam Second The second element of the pair to emplace and override. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0, + typename ... Args> + Self& emplace_override(Args&&... args) { + this->override(); + + flecs::emplace(this->m_world, this->m_id, + ecs_pair(_::cpp_type::id(this->m_world), + _::cpp_type::id(this->m_world)), + FLECS_FWD(args)...); + + return to_base(); + } + + /** Enable an entity. + * Enabled entities are matched with systems and can be searched with + * queries. + */ + Self& enable() { + ecs_enable(this->m_world, this->m_id, true); + return to_base(); + } + + /** Disable an entity. + * Disabled entities are not matched with systems and cannot be searched + * with queries, unless explicitly specified in the query expression. + */ + Self& disable() { + ecs_enable(this->m_world, this->m_id, false); + return to_base(); + } + + /** Enable an id. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @param id The id to enable. + * @param toggle True to enable, false to disable (default = true). + */ + Self& enable(flecs::id_t id, bool toggle = true) { + ecs_enable_id(this->m_world, this->m_id, id, toggle); + return to_base(); + } + + /** Enable a component. + * @see enable(flecs::id_t id) + * + * @tparam T The component to enable. + */ + template + Self& enable() { + return this->enable(_::cpp_type::id(this->m_world)); + } + + /** Enable a pair. + * @see enable(flecs::id_t id) + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + Self& enable(flecs::id_t first, flecs::id_t second) { + return this->enable(ecs_pair(first, second)); + } + + /** Enable a pair. + * @see enable(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + Self& enable(flecs::id_t second) { + return this->enable(_::cpp_type::id(), second); + } + + /** Enable a pair. + * @see enable(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template + Self& enable() { + return this->enable(_::cpp_type::id()); + } + + /** Disable an id. + * This sets the enabled bit for this id. If this is the first time + * the id is enabled or disabled, the bitset is added. + * + * @param id The id to disable. + */ + Self& disable(flecs::id_t id) { + return this->enable(id, false); + } + + /** Disable a component. + * @see disable(flecs::id_t id) + * + * @tparam T The component to enable. + */ + template + Self& disable() { + return this->disable(_::cpp_type::id()); + } + + /** Disable a pair. + * @see disable(flecs::id_t id) + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + Self& disable(flecs::id_t first, flecs::id_t second) { + return this->disable(ecs_pair(first, second)); + } + + /** Disable a pair. + * @see disable(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + Self& disable(flecs::id_t second) { + return this->disable(_::cpp_type::id(), second); + } + + /** Disable a pair. + * @see disable(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template + Self& disable() { + return this->disable(_::cpp_type::id()); + } + + Self& set_ptr(entity_t comp, size_t size, const void *ptr) { + ecs_set_id(this->m_world, this->m_id, comp, size, ptr); + return to_base(); + } + + Self& set_ptr(entity_t comp, const void *ptr) { + const flecs::Component *cptr = ecs_get( + this->m_world, comp, EcsComponent); + + /* Can't set if it's not a component */ + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + + return set_ptr(comp, cptr->size, ptr); + } + + template::value && is_actual::value> = 0 > + Self& set(T&& value) { + flecs::set(this->m_world, this->m_id, FLECS_FWD(value)); + return to_base(); + } + + template::value && is_actual::value > = 0> + Self& set(const T& value) { + flecs::set(this->m_world, this->m_id, value); + return to_base(); + } + + template, if_not_t< + is_callable::value || is_actual::value > = 0> + Self& set(A&& value) { + flecs::set(this->m_world, this->m_id, FLECS_FWD(value)); + return to_base(); + } + + template, if_not_t< + is_callable::value || is_actual::value > = 0> + Self& set(const A& value) { + flecs::set(this->m_world, this->m_id, value); + return to_base(); + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& set(A&& value) { + flecs::set

(this->m_world, this->m_id, FLECS_FWD(value)); + return to_base(); + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& set(const A& value) { + flecs::set

(this->m_world, this->m_id, value); + return to_base(); + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param value The value to set. + */ + template ::value > = 0> + Self& set(Second second, const First& value) { + auto first = _::cpp_type::id(this->m_world); + flecs::set(this->m_world, this->m_id, value, + ecs_pair(first, second)); + return to_base(); + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param value The value to set. + */ + template ::value > = 0> + Self& set(Second second, First&& value) { + auto first = _::cpp_type::id(this->m_world); + flecs::set(this->m_world, this->m_id, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @param constant The enum constant. + * @param value The value to set. + */ + template ::value > = 0> + Self& set(Second constant, const First& value) { + const auto& et = enum_type(this->m_world); + flecs::entity_t second = et.entity(constant); + return set(second, value); + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair. + * @param value The value to set. + */ + template + Self& set_second(entity_t first, const Second& value) { + auto second = _::cpp_type::id(this->m_world); + flecs::set(this->m_world, this->m_id, value, + ecs_pair(first, second)); + return to_base(); + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair. + * @param value The value to set. + */ + template + Self& set_second(entity_t first, Second&& value) { + auto second = _::cpp_type::id(this->m_world); + flecs::set(this->m_world, this->m_id, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } + + template + Self& set_second(const Second& value) { + flecs::set>(this->m_world, this->m_id, value); + return to_base(); + } + + /** Set 1..N components. + * This operation accepts a callback with as arguments the components to + * set. If the entity does not have all of the provided components, they + * will be added. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. When this operation is called + * while deferred, its performance is equivalent to that of calling get_mut + * for each component separately. + * + * The operation will invoke modified for each component after the callback + * has been invoked. + * + * @param func The callback to invoke. + */ + template ::value > = 0> + Self& set(const Func& func); + + /** Emplace component. + * Emplace constructs a component in the storage, which prevents calling the + * destructor on the value passed into the function. + * + * Emplace attempts the following signatures to construct the component: + * + * @code + * T{Args...} + * T{flecs::entity, Args...} + * @endcode + * + * If the second signature matches, emplace will pass in the current entity + * as argument to the constructor, which is useful if the component needs + * to be aware of the entity to which it has been added. + * + * Emplace may only be called for components that have not yet been added + * to the entity. + * + * @tparam T the component to emplace + * @param args The arguments to pass to the constructor of T + */ + template> + Self& emplace(Args&&... args) { + flecs::emplace(this->m_world, this->m_id, + _::cpp_type::id(this->m_world), FLECS_FWD(args)...); + return to_base(); + } + + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& emplace(Args&&... args) { + flecs::emplace(this->m_world, this->m_id, + ecs_pair(_::cpp_type::id(this->m_world), + _::cpp_type::id(this->m_world)), + FLECS_FWD(args)...); + return to_base(); + } + + template + Self& emplace_first(flecs::entity_t second, Args&&... args) { + flecs::emplace(this->m_world, this->m_id, + ecs_pair(_::cpp_type::id(this->m_world), second), + FLECS_FWD(args)...); + return to_base(); + } + + template + Self& emplace_second(flecs::entity_t first, Args&&... args) { + flecs::emplace(this->m_world, this->m_id, + ecs_pair(first, _::cpp_type::id(this->m_world)), + FLECS_FWD(args)...); + return to_base(); + } + + /** Entities created in function will have the current entity. + * This operation is thread safe. + * + * @param func The function to call. + */ + template + Self& with(const Func& func) { + ecs_id_t prev = ecs_set_with(this->m_world, this->m_id); + func(); + ecs_set_with(this->m_world, prev); + return to_base(); + } + + /** Entities created in function will have (First, this). + * This operation is thread safe. + * + * @tparam First The first element of the pair + * @param func The function to call. + */ + template + Self& with(const Func& func) { + with(_::cpp_type::id(this->m_world), func); + return to_base(); + } + + /** Entities created in function will have (first, this). + * This operation is thread safe. + * + * @param first The first element of the pair. + * @param func The function to call. + */ + template + Self& with(entity_t first, const Func& func) { + ecs_id_t prev = ecs_set_with(this->m_world, + ecs_pair(first, this->m_id)); + func(); + ecs_set_with(this->m_world, prev); + return to_base(); + } + + /** The function will be ran with the scope set to the current entity. */ + template + Self& scope(const Func& func) { + ecs_entity_t prev = ecs_set_scope(this->m_world, this->m_id); + func(); + ecs_set_scope(this->m_world, prev); + return to_base(); + } + + /** Return world scoped to entity */ + scoped_world scope() const { + return scoped_world(m_world, m_id); + } + + /* Set the entity name. + */ + Self& set_name(const char *name) { + ecs_set_name(this->m_world, this->m_id, name); + return to_base(); + } + + /* Set entity alias. + */ + Self& set_alias(const char *name) { + ecs_set_alias(this->m_world, this->m_id, name); + return to_base(); + } + +# ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/entity_builder.inl + * @brief Doc entity builder mixin. + */ + +/** Set human readable name. + * This adds `(flecs.doc.Description, flecs.Name)` to the entity. + * + * @see ecs_doc_set_name() + * @see flecs::doc::set_name() + * @see flecs::entity_view::doc_name() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc + */ +Self& set_doc_name(const char *name) { + ecs_doc_set_name(m_world, m_id, name); + return to_base(); +} + +/** Set brief description. + * This adds `(flecs.doc.Description, flecs.doc.Brief)` to the entity. + * + * @see ecs_doc_set_brief() + * @see flecs::doc::set_brief() + * @see flecs::entity_view::doc_brief() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc + */ +Self& set_doc_brief(const char *brief) { + ecs_doc_set_brief(m_world, m_id, brief); + return to_base(); +} + +/** Set detailed description. + * This adds `(flecs.doc.Description, flecs.doc.Detail)` to the entity. + * + * @see ecs_doc_set_detail() + * @see flecs::doc::set_detail() + * @see flecs::entity_view::doc_detail() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc + */ +Self& set_doc_detail(const char *detail) { + ecs_doc_set_detail(m_world, m_id, detail); + return to_base(); +} + +/** Set link to external documentation. + * This adds `(flecs.doc.Description, flecs.doc.Link)` to the entity. + * + * @see ecs_doc_set_link() + * @see flecs::doc::set_link() + * @see flecs::entity_view::doc_link() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc + */ +Self& set_doc_link(const char *link) { + ecs_doc_set_link(m_world, m_id, link); + return to_base(); +} + +/** Set doc color. + * This adds `(flecs.doc.Description, flecs.doc.Color)` to the entity. + * + * @see ecs_doc_set_color() + * @see flecs::doc::set_color() + * @see flecs::entity_view::doc_color() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc + */ +Self& set_doc_color(const char *link) { + ecs_doc_set_color(m_world, m_id, link); + return to_base(); +} + +# endif + +# ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/entity_builder.inl + * @brief Meta entity builder mixin. + */ + +/** + * @memberof flecs::entity_view + * @ingroup cpp_addons_meta + * + * @{ + */ + +/** Make entity a unit */ +Self& unit( + const char *symbol, + flecs::entity_t prefix = 0, + flecs::entity_t base = 0, + flecs::entity_t over = 0, + int32_t factor = 0, + int32_t power = 0) +{ + ecs_unit_desc_t desc = {}; + desc.entity = this->m_id; + desc.symbol = const_cast(symbol); /* safe, will be copied in */ + desc.base = base; + desc.over = over; + desc.prefix = prefix; + desc.translation.factor = factor; + desc.translation.power = power; + ecs_unit_init(this->world(), &desc); + + return to_base(); +} + +/** Make entity a derived unit */ +Self& unit( + flecs::entity_t prefix = 0, + flecs::entity_t base = 0, + flecs::entity_t over = 0, + int32_t factor = 0, + int32_t power = 0) +{ + ecs_unit_desc_t desc = {}; + desc.entity = this->m_id; + desc.base = base; + desc.over = over; + desc.prefix = prefix; + desc.translation.factor = factor; + desc.translation.power = power; + ecs_unit_init(this->world(), &desc); + + return to_base(); +} + +/** Make entity a derived unit */ +Self& unit_prefix( + const char *symbol, + int32_t factor = 0, + int32_t power = 0) +{ + ecs_unit_prefix_desc_t desc = {}; + desc.entity = this->m_id; + desc.symbol = const_cast(symbol); /* safe, will be copied in */ + desc.translation.factor = factor; + desc.translation.power = power; + ecs_unit_prefix_init(this->world(), &desc); + + return to_base(); +} + +/** Add quantity to unit */ +Self& quantity(flecs::entity_t quantity) { + ecs_add_pair(this->world(), this->id(), flecs::Quantity, quantity); + return to_base(); +} + +/** Make entity a unity prefix */ +template +Self& quantity() { + return this->quantity(_::cpp_type::id(this->world())); +} + +/** Make entity a quantity */ +Self& quantity() { + ecs_add_id(this->world(), this->id(), flecs::Quantity); + return to_base(); +} + +/** @} */ + +# endif + +# ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/entity_builder.inl + * @brief JSON entity mixin. + */ + +/** Set component from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +Self& set_json( + flecs::id_t e, + const char *json, + flecs::from_json_desc_t *desc = nullptr) +{ + flecs::entity_t type = ecs_get_typeid(m_world, e); + if (!type) { + ecs_err("id is not a type"); + return to_base(); + } + + void *ptr = ecs_get_mut_id(m_world, m_id, e); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_ptr_from_json(m_world, type, ptr, json, desc); + ecs_modified_id(m_world, m_id, e); + + return to_base(); +} + +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +Self& set_json( + flecs::entity_t r, + flecs::entity_t t, + const char *json, + flecs::from_json_desc_t *desc = nullptr) +{ + return set_json(ecs_pair(r, t), json, desc); +} + +/** Set component from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +template +Self& set_json( + const char *json, + flecs::from_json_desc_t *desc = nullptr) +{ + return set_json(_::cpp_type::id(m_world), json, desc); +} + +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +template +Self& set_json( + const char *json, + flecs::from_json_desc_t *desc = nullptr) +{ + return set_json( + _::cpp_type::id(m_world), + _::cpp_type::id(m_world), + json, desc); +} + +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +template +Self& set_json( + flecs::entity_t t, + const char *json, + flecs::from_json_desc_t *desc = nullptr) +{ + return set_json( + _::cpp_type::id(m_world), t, + json, desc); +} + +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +template +Self& set_json_second( + flecs::entity_t r, + const char *json, + flecs::from_json_desc_t *desc = nullptr) +{ + return set_json( + r, _::cpp_type::id(m_world), + json, desc); +} + +# endif + +/** + * @file addons/cpp/mixins/event/entity_builder.inl + * @brief Event entity mixin. + */ + +/** Observe event on entity + * + * @memberof flecs::entity_builder + * + * @param evt The event id. + * @param callback The observer callback. + * @return Event builder. + */ +template +Self& observe(flecs::entity_t evt, Func&& callback); + +/** Observe event on entity + * + * @memberof flecs::entity_builder + * + * @tparam Evt The event type. + * @param callback The observer callback. + * @return Event builder. + */ +template +Self& observe(Func&& callback); + +/** Observe event on entity + * + * @memberof flecs::entity_builder + * + * @param callback The observer callback. + * @return Event builder. + */ +template +Self& observe(Func&& callback); + + + + +protected: + Self& to_base() { + return *static_cast(this); + } +}; + +} + + +/** + * @defgroup cpp_entities Entities + * @ingroup cpp_core + * Entity operations. + * + * @{ + */ + +namespace flecs +{ + +/** Entity. + * Class with read/write operations for entities. + * + * @ingroup cpp_entities +*/ +struct entity : entity_builder +{ + entity() : entity_builder() { } + + /** Create entity. + * + * @param world The world in which to create the entity. + */ + explicit entity(world_t *world) + : entity_builder() + { + m_world = world; + m_id = ecs_new(world, 0); + } + + /** Wrap an existing entity id. + * + * @param world The world in which the entity is created. + * @param id The entity id. + */ + explicit entity(const flecs::world_t *world, flecs::id_t id) { + m_world = const_cast(world); + m_id = id; + } + + /** 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. + */ + explicit entity(world_t *world, const char *name) + : entity_builder() + { + m_world = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = "::"; + desc.root_sep = "::"; + m_id = ecs_entity_init(world, &desc); + } + + /** Conversion from flecs::entity_t to flecs::entity. + * + * @param id The entity_t value to convert. + */ + explicit entity(entity_t id) + : entity_builder( nullptr, 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. + * @return Pointer to the component value. + */ + template + T* get_mut() const { + auto comp_id = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast(ecs_get_mut_id(m_world, m_id, comp_id)); + } + + /** 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 comp The component to get. + * @return Pointer to the component value. + */ + void* get_mut(entity_t comp) const { + return ecs_get_mut_id(m_world, m_id, comp); + } + + /** Get mutable pointer for a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first part of the pair. + * @tparam Second the second part of the pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + A* get_mut() const { + return static_cast(ecs_get_mut_id(m_world, m_id, ecs_pair( + _::cpp_type::id(m_world), + _::cpp_type::id(m_world)))); + } + + /** Get mutable pointer for the first element of a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first part of the pair. + * @param second The second element of the pair. + */ + template + First* get_mut(entity_t second) const { + auto comp_id = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_mut_id(m_world, m_id, ecs_pair(comp_id, second))); + } + + /** Get mutable pointer for a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * first nor second element of the pair is a component, the operation will + * fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + void* get_mut(entity_t first, entity_t second) const { + return ecs_get_mut_id(m_world, m_id, ecs_pair(first, second)); + } + + /** Get mutable pointer for the second element of a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Second The second element of the pair. + * @param first The first element of the pair. + */ + template + Second* get_mut_second(entity_t first) const { + auto second = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast( + ecs_get_mut_id(m_world, m_id, ecs_pair(first, second))); + } + + /** Signal that component was modified. + * + * @tparam T component that was modified. + */ + template + void modified() const { + auto comp_id = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + this->modified(comp_id); + } + + /** Signal that the first element of a pair was modified. + * + * @tparam First The first part of the pair. + * @tparam Second the second part of the pair. + */ + template + void modified() const { + this->modified(_::cpp_type::id(m_world)); + } + + /** Signal that the first part of a pair was modified. + * + * @tparam First The first part of the pair. + * @param second The second element of the pair. + */ + template + void modified(entity_t second) const { + auto first = _::cpp_type::id(m_world); + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + this->modified(first, second); + } + + /** Signal that a pair has modified (untyped). + * If neither the first or second element of the pair are a component, the + * operation will fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + void modified(entity_t first, entity_t second) const { + this->modified(ecs_pair(first, second)); + } + + /** Signal that component was modified. + * + * @param comp component that was modified. + */ + void modified(entity_t comp) const { + ecs_modified_id(m_world, m_id, comp); + } + + /** 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 { + return ref(m_world, m_id, _::cpp_type::id(m_world)); + } + + template , + typename A = actual_type_t

> + ref get_ref() const { + return ref(m_world, m_id, + ecs_pair(_::cpp_type::id(m_world), + _::cpp_type::id(m_world))); + } + + template + ref get_ref(flecs::entity_t second) const { + return ref(m_world, m_id, + ecs_pair(_::cpp_type::id(m_world), second)); + } + + template + ref get_ref_second(flecs::entity_t first) const { + return ref(m_world, m_id, + ecs_pair(first, _::cpp_type::id(m_world))); + } + + /** Recursively flatten relationship. + * @see ecs_flatten + */ + void flatten(flecs::entity_t r, const ecs_flatten_desc_t *desc = nullptr) { + ecs_flatten(m_world, ecs_pair(r, m_id), desc); + } + + /** Clear an entity. + * This operation removes all components from an entity without recycling + * the entity id. + */ + void clear() const { + ecs_clear(m_world, m_id); + } + + /** Delete an entity. + * Entities have to be deleted explicitly, and are not deleted when the + * entity object goes out of scope. + */ + void destruct() const { + ecs_delete(m_world, m_id); + } + + /** Return entity as entity_view. + * This returns an entity_view instance for the entity which is a readonly + * version of the entity class. + * + * This is similar to a regular upcast, except that this method ensures that + * the entity_view instance is instantiated with a world vs. a stage, which + * a regular upcast does not guarantee. + */ + flecs::entity_view view() const { + return flecs::entity_view( + const_cast(ecs_get_world(m_world)), m_id); + } + + /** Entity id 0. + * This function is useful when the API must provide an entity that + * belongs to a world, but the entity id is 0. + * + * @param world The world. + */ + static + flecs::entity null(const flecs::world_t *world) { + flecs::entity result; + result.m_world = const_cast(world); + return result; + } + + static + flecs::entity null() { + return flecs::entity(); + } + +# ifdef FLECS_JSON + +/** Deserialize entity to JSON. + * + * @memberof flecs::entity + * @ingroup cpp_addons_json + */ +const char* from_json(const char *json) { + return ecs_entity_from_json(m_world, m_id, json, nullptr); +} + +# endif +}; + +} // namespace flecs + +/** @} */ + +/** + * @file addons/cpp/delegate.hpp + * @brief Wrappers around C++ functions that provide callbacks for C APIs. + */ + +#pragma once + +namespace flecs +{ + +namespace _ +{ + +// Binding ctx for component hooks +struct component_binding_ctx { + void *on_add = nullptr; + void *on_remove = nullptr; + void *on_set = nullptr; + ecs_ctx_free_t free_on_add = nullptr; + ecs_ctx_free_t free_on_remove = nullptr; + ecs_ctx_free_t free_on_set = nullptr; + + ~component_binding_ctx() { + if (on_add && free_on_add) { + free_on_add(on_add); + } + if (on_remove && free_on_remove) { + free_on_remove(on_remove); + } + if (on_set && free_on_set) { + free_on_set(on_set); + } + } +}; + +// Utility to convert template argument pack to array of term ptrs +struct term_ptr { + void *ptr; + bool is_ref; +}; + +template +struct term_ptrs { + using array = flecs::array<_::term_ptr, sizeof...(Components)>; + + bool populate(const ecs_iter_t *iter) { + return populate(iter, 0, static_cast< + remove_reference_t< + remove_pointer_t> + *>(nullptr)...); + } + + array m_terms; + +private: + /* Populate terms array without checking for references */ + bool populate(const ecs_iter_t*, size_t) { return false; } + + template + bool populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) { + m_terms[index].ptr = iter->ptrs[index]; + bool is_ref = iter->sources && iter->sources[index] != 0; + m_terms[index].is_ref = is_ref; + is_ref |= populate(iter, index + 1, comps ...); + return is_ref; + } +}; + +struct delegate { }; + +// Template that figures out from the template parameters of a query/system +// how to pass the value to the each callback +template +struct each_column { }; + +// Base class +struct each_column_base { + each_column_base(const _::term_ptr& term, size_t row) + : m_term(term), m_row(row) { } + +protected: + const _::term_ptr& m_term; + size_t m_row; +}; + +// If type is not a pointer, return a reference to the type (default case) +template +struct each_column::value && + !is_empty>::value && is_actual::value > > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T& get_row() { + return static_cast(this->m_term.ptr)[this->m_row]; + } +}; + +// If argument type is not the same as actual component type, return by value. +// This requires that the actual type can be converted to the type. +// A typical scenario where this happens is when using flecs::pair types. +template +struct each_column::value && + !is_empty>::value && !is_actual::value> > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + return static_cast*>(this->m_term.ptr)[this->m_row]; + } +}; + + +// If type is empty (indicating a tag) the query will pass a nullptr. To avoid +// returning nullptr to reference arguments, return a temporary value. +template +struct each_column>::value && + !is_pointer::value > > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + return actual_type_t(); + } +}; + + +// If type is a pointer (indicating an optional value) return the type as is +template +struct each_column::value && + !is_empty>::value > > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + actual_type_t get_row() { + if (this->m_term.ptr) { + return &static_cast>(this->m_term.ptr)[this->m_row]; + } else { + // optional argument doesn't have a value + return nullptr; + } + } +}; + +// If the query contains component references to other entities, check if the +// current argument is one. +template +struct each_ref_column : public each_column { + each_ref_column(const _::term_ptr& term, size_t row) + : each_column(term, row) { + + if (term.is_ref) { + // If this is a reference, set the row to 0 as a ref always is a + // single value, not an array. This prevents the application from + // having to do an if-check on whether the column is owned. + // + // This check only happens when the current table being iterated + // over caused the query to match a reference. The check is + // performed once per iterated table. + this->m_row = 0; + } + } +}; + +template +struct each_delegate : public delegate { + // If the number of arguments in the function signature is one more than the + // number of components in the query, an extra entity arg is required. + static constexpr bool PassEntity = + (sizeof...(Components) + 1) == (arity::value); + + // If the number of arguments in the function is two more than the number of + // components in the query, extra iter + index arguments are required. + static constexpr bool PassIter = + (sizeof...(Components) + 2) == (arity::value); + + static_assert(arity::value > 0, + "each() must have at least one argument"); + + using Terms = typename term_ptrs::array; + + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit each_delegate(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + explicit each_delegate(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the delegate, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs terms; + + if (terms.populate(iter)) { + invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + } else { + invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); + } + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + + // Create instance of delegate + static each_delegate* make(const Func& func) { + return FLECS_NEW(each_delegate)(func); + } + + // Function that can be used as callback to free delegate + static void free(void *obj) { + _::free_obj(static_cast(obj)); + } + + // Static function to call for component on_add hook + static void run_add(ecs_iter_t *iter) { + component_binding_ctx *ctx = reinterpret_cast( + iter->binding_ctx); + iter->binding_ctx = ctx->on_add; + run(iter); + } + + // Static function to call for component on_remove hook + static void run_remove(ecs_iter_t *iter) { + component_binding_ctx *ctx = reinterpret_cast( + iter->binding_ctx); + iter->binding_ctx = ctx->on_remove; + run(iter); + } + + // Static function to call for component on_set hook + static void run_set(ecs_iter_t *iter) { + component_binding_ctx *ctx = reinterpret_cast( + iter->binding_ctx); + iter->binding_ctx = ctx->on_set; + run(iter); + } + + // Each delegates always use instanced iterators + static bool instanced() { + return true; + } + +private: + // Number of function arguments is one more than number of components, pass + // entity as argument. + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + ECS_TABLE_LOCK(iter->world, iter->table); + + ecs_world_t *world = iter->world; + size_t count = static_cast(iter->count); + + ecs_assert(count > 0, ECS_INVALID_OPERATION, + "no entities returned, use each() without flecs::entity argument"); + + for (size_t i = 0; i < count; i ++) { + func(flecs::entity(world, iter->entities[i]), + (ColumnType< remove_reference_t >(comps, i) + .get_row())...); + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + } + + // Number of function arguments is two more than number of components, pass + // iter + index as argument. + template class ColumnType, + typename... Args, int Enabled = PassIter, if_t< + sizeof...(Components) == sizeof...(Args) && Enabled> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + func(it, i, (ColumnType< remove_reference_t >(comps, i) + .get_row())...); + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + } + + // Number of function arguments is equal to number of components, no entity + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + func( (ColumnType< remove_reference_t >(comps, i) + .get_row())...); + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + } + + template class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + invoke_callback( + iter, func, index + 1, columns, comps..., columns[index]); + } + + Func m_func; +}; + +template +struct find_delegate : public delegate { + // If the number of arguments in the function signature is one more than the + // number of components in the query, an extra entity arg is required. + static constexpr bool PassEntity = + (sizeof...(Components) + 1) == (arity::value); + + // If the number of arguments in the function is two more than the number of + // components in the query, extra iter + index arguments are required. + static constexpr bool PassIter = + (sizeof...(Components) + 2) == (arity::value); + + static_assert(arity::value > 0, + "each() must have at least one argument"); + + using Terms = typename term_ptrs::array; + + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit find_delegate(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + explicit find_delegate(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the delegate, such as what happens when + // iterating a query. + flecs::entity invoke(ecs_iter_t *iter) const { + term_ptrs terms; + + if (terms.populate(iter)) { + return invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + } else { + return invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); + } + } + + // Find delegates always use instanced iterators + static bool instanced() { + return true; + } + +private: + // Number of function arguments is one more than number of components, pass + // entity as argument. + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + ECS_TABLE_LOCK(iter->world, iter->table); + + ecs_world_t *world = iter->world; + size_t count = static_cast(iter->count); + flecs::entity result; + + ecs_assert(count > 0, ECS_INVALID_OPERATION, + "no entities returned, use find() without flecs::entity argument"); + + for (size_t i = 0; i < count; i ++) { + if (func(flecs::entity(world, iter->entities[i]), + (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + result = flecs::entity(world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; + } + + // Number of function arguments is two more than number of components, pass + // iter + index as argument. + template class ColumnType, + typename... Args, int Enabled = PassIter, if_t< + sizeof...(Components) == sizeof...(Args) && Enabled> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + flecs::entity result; + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func(it, i, (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + result = flecs::entity(iter->world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; + } + + // Number of function arguments is equal to number of components, no entity + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + flecs::entity result; + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func( (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + result = flecs::entity(iter->world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; + } + + template class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static flecs::entity invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + return invoke_callback( + iter, func, index + 1, columns, comps..., columns[index]); + } + + Func m_func; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system iterate action +//////////////////////////////////////////////////////////////////////////////// + +template +struct iter_delegate : delegate { +private: + static constexpr bool IterOnly = arity::value == 1; + + using Terms = typename term_ptrs::array; + +public: + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit iter_delegate(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + explicit iter_delegate(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the delegate, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs terms; + terms.populate(iter); + invoke_callback(iter, m_func, 0, terms.m_terms); + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + + // Instancing needs to be enabled explicitly for iter delegates + static bool instanced() { + return false; + } + +private: + template = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t, Terms&, Args...) + { + flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + + func(it); + + ECS_TABLE_UNLOCK(iter->world, iter->table); + } + + template = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, size_t, + Terms&, Targs... comps) + { + flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + + func(it, ( static_cast< + remove_reference_t< + remove_pointer_t< + actual_type_t > >* > + (comps.ptr))...); + + ECS_TABLE_UNLOCK(iter->world, iter->table); + } + + template = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Targs... comps) + { + invoke_callback(iter, func, index + 1, columns, comps..., + columns[index]); + } + + Func m_func; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke an entity observer delegate +//////////////////////////////////////////////////////////////////////////////// + +template +struct entity_observer_delegate : delegate { + explicit entity_observer_delegate(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + invoke(iter); + } +private: + template ::value == 1> = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->m_func(flecs::entity(iter->world, ecs_field_src(iter, 1))); + } + + template ::value == 0> = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->m_func(); + } + + Func m_func; +}; + +template +struct entity_payload_observer_delegate : delegate { + explicit entity_payload_observer_delegate(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + invoke(iter); + } + +private: + template ::value == 1> = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast( + iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->param != nullptr, ECS_INVALID_OPERATION, + "entity observer invoked without payload"); + + Event *data = static_cast(iter->param); + self->m_func(*data); + } + + template ::value == 2> = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast( + iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->param != nullptr, ECS_INVALID_OPERATION, + "entity observer invoked without payload"); + + Event *data = static_cast(iter->param); + self->m_func(flecs::entity(iter->world, ecs_field_src(iter, 1)), *data); + } + + Func m_func; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility to invoke callback on entity if it has components in signature +//////////////////////////////////////////////////////////////////////////////// + +template +struct entity_with_delegate_impl; + +template +struct entity_with_delegate_impl> { + using ColumnArray = flecs::array; + using ArrayType = flecs::array; + using DummyArray = flecs::array; + using IdArray = flecs::array; + + static bool const_args() { + static flecs::array is_const_args ({ + flecs::is_const>::value... + }); + + for (auto is_const : is_const_args) { + if (!is_const) { + return false; + } + } + return true; + } + + static + bool get_ptrs(world_t *world, const ecs_record_t *r, ecs_table_t *table, + ArrayType& ptrs) + { + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (!ecs_table_column_count(table)) { + return false; + } + + /* table_index_of needs real world */ + const flecs::world_t *real_world = ecs_get_world(world); + + /* Get column indices for components */ + ColumnArray columns ({ + ecs_table_get_column_index(real_world, table, + _::cpp_type().id(world))... + }); + + /* Get pointers for columns for entity */ + size_t i = 0; + for (int32_t column : columns) { + if (column == -1) { + return false; + } + + ptrs[i ++] = ecs_record_get_column(r, column, 0); + } + + return true; + } + + static bool get_mut_ptrs(world_t *world, ecs_entity_t e, ArrayType& ptrs) { + /* Get pointers w/get_mut */ + size_t i = 0; + DummyArray dummy ({ + (ptrs[i ++] = ecs_get_mut_id(world, e, + _::cpp_type().id(world)), 0)... + }); + + return true; + } + + template + static bool invoke_read(world_t *world, entity_t e, const Func& func) { + const ecs_record_t *r = ecs_read_begin(world, e); + if (!r) { + return false; + } + + ecs_table_t *table = r->table; + if (!table) { + return false; + } + + ArrayType ptrs; + bool has_components = get_ptrs(world, r, table, ptrs); + if (has_components) { + invoke_callback(func, 0, ptrs); + } + + ecs_read_end(r); + + return has_components; + } + + template + static bool invoke_write(world_t *world, entity_t e, const Func& func) { + ecs_record_t *r = ecs_write_begin(world, e); + if (!r) { + return false; + } + + ecs_table_t *table = r->table; + if (!table) { + return false; + } + + ArrayType ptrs; + bool has_components = get_ptrs(world, r, table, ptrs); + if (has_components) { + invoke_callback(func, 0, ptrs); + } + + ecs_write_end(r); + + return has_components; + } + + template + static bool invoke_get(world_t *world, entity_t e, const Func& func) { + if (const_args()) { + return invoke_read(world, e, func); + } else { + return invoke_write(world, e, func); + } + } + + // Utility for storing id in array in pack expansion + static size_t store_added(IdArray& added, size_t elem, ecs_table_t *prev, + ecs_table_t *next, id_t id) + { + // Array should only contain ids for components that are actually added, + // so check if the prev and next tables are different. + if (prev != next) { + added[elem] = id; + elem ++; + } + return elem; + } + + template + static bool invoke_get_mut(world_t *world, entity_t id, const Func& func) { + flecs::world w(world); + + ArrayType ptrs; + ecs_table_t *table = NULL; + + // When not deferred take the fast path. + if (!w.is_deferred()) { + // Bit of low level code so we only do at most one table move & one + // entity lookup for the entire operation. + + // Make sure the object is not a stage. Operations on a stage are + // only allowed when the stage is in deferred mode, which is when + // the world is in readonly mode. + ecs_assert(!w.is_stage(), ECS_INVALID_PARAMETER, NULL); + + // Find table for entity + ecs_record_t *r = ecs_record_find(world, id); + if (r) { + table = r->table; + } + + // Find destination table that has all components + ecs_table_t *prev = table, *next; + size_t elem = 0; + IdArray added; + + // Iterate components, only store added component ids in added array + DummyArray dummy_before ({ ( + next = ecs_table_add_id(world, prev, w.id()), + elem = store_added(added, elem, prev, next, w.id()), + prev = next, 0 + )... }); + (void)dummy_before; + + // If table is different, move entity straight to it + if (table != next) { + ecs_type_t ids; + ids.array = added.ptr(); + ids.count = static_cast(elem); + ecs_commit(world, id, r, next, &ids, NULL); + table = next; + } + + if (!get_ptrs(w, r, table, ptrs)) { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + ECS_TABLE_LOCK(world, table); + + // When deferred, obtain pointers with regular get_mut + } else { + get_mut_ptrs(world, id, ptrs); + } + + invoke_callback(func, 0, ptrs); + + if (!w.is_deferred()) { + ECS_TABLE_UNLOCK(world, table); + } + + // Call modified on each component + DummyArray dummy_after ({ + ( ecs_modified_id(world, id, w.id()), 0)... + }); + (void)dummy_after; + + return true; + } + +private: + template = 0> + static void invoke_callback( + const Func& f, size_t, ArrayType&, TArgs&& ... comps) + { + f(*static_cast::type*>(comps)...); + } + + template = 0> + static void invoke_callback(const Func& f, size_t arg, ArrayType& ptrs, + TArgs&& ... comps) + { + invoke_callback(f, arg + 1, ptrs, comps..., ptrs[arg]); + } +}; + +template +struct entity_with_delegate { + static_assert(function_traits::value, "type is not callable"); +}; + +template +struct entity_with_delegate::value > > + : entity_with_delegate_impl< arg_list_t > +{ + static_assert(function_traits::arity > 0, + "function must have at least one argument"); +}; + +} // namespace _ + +// Experimental: allows using the each delegate for use cases outside of flecs +template +using delegate = _::each_delegate::type, Args...>; + +} // namespace flecs + +/** + * @file addons/cpp/utils/iterable.hpp + * @brief Base class for iterable objects, like queries. + */ + +namespace flecs { + +template +struct iter_iterable; + +template +struct page_iterable; + +template +struct worker_iterable; + +template +struct iterable { + + /** Each iterator. + * The "each" iterator accepts a function that is invoked for each matching + * entity. The following function signatures are valid: + * - func(flecs::entity e, Components& ...) + * - func(flecs::iter& it, size_t index, Components& ....) + * - func(Components& ...) + * + * Each iterators are automatically instanced. + */ + template + void each(Func&& func) const { + each(nullptr, FLECS_FWD(func)); + } + + template + void each(flecs::world_t *world, Func&& func) const { + iterate<_::each_delegate>(world, FLECS_FWD(func), + this->next_each_action()); + } + + template + void each(flecs::iter& it, Func&& func) const { + iterate<_::each_delegate>(it.world(), FLECS_FWD(func), + this->next_each_action()); + } + + template + void each(flecs::entity e, Func&& func) const { + iterate<_::each_delegate>(e.world(), FLECS_FWD(func), + this->next_each_action()); + } + + template + flecs::entity find(Func&& func) const { + return iterate_find<_::find_delegate>(nullptr, FLECS_FWD(func), + this->next_each_action()); + } + + /** Iter iterator. + * The "iter" iterator accepts a function that is invoked for each matching + * table. The following function signatures are valid: + * - func(flecs::iter& it, Components* ...) + * - func(Components& ...) + * + * Iter iterators are not automatically instanced. When a result contains + * shared components, entities of the result will be iterated one by one. + * This ensures that applications can't accidentally read out of bounds by + * accessing a shared component as an array. + */ + template + void iter(Func&& func) const { + iterate<_::iter_delegate>(nullptr, FLECS_FWD(func), + this->next_action()); + } + + template + void iter(flecs::world_t *world, Func&& func) const { + iterate<_::iter_delegate>(world, FLECS_FWD(func), + this->next_action()); + } + + template + void iter(flecs::iter& it, Func&& func) const { + iterate<_::iter_delegate>(it.world(), FLECS_FWD(func), + this->next_action()); + } + + template + void iter(flecs::entity e, Func&& func) const { + iterate<_::iter_delegate>(e.world(), FLECS_FWD(func), + this->next_action()); + } + + /** Create iterator. + * Create an iterator object that can be modified before iterating. + */ + iter_iterable iter(flecs::world_t *world = nullptr) const; + + /** Page iterator. + * Create an iterator that limits the returned entities with offset/limit. + * + * @param offset How many entities to skip. + * @param limit The maximum number of entities to return. + * @return Iterable that can be iterated with each/iter. + */ + page_iterable page(int32_t offset, int32_t limit); + + /** Worker iterator. + * Create an iterator that divides the number of matched entities across + * a number of resources. + * + * @param index The index of the current resource. + * @param count The total number of resources to divide entities between. + * @return Iterable that can be iterated with each/iter. + */ + worker_iterable worker(int32_t index, int32_t count); + + /** Return number of entities matched by iterable. */ + int32_t count() const { + return this->iter().count(); + } + + /** Return whether iterable has any matches. */ + bool is_true() const { + return this->iter().is_true(); + } + + /** Return first entity matched by iterable. */ + flecs::entity first() const { + return this->iter().first(); + } + + virtual ~iterable() { } +protected: + friend iter_iterable; + friend page_iterable; + friend worker_iterable; + + virtual ecs_iter_t get_iter(flecs::world_t *stage) const = 0; + virtual ecs_iter_next_action_t next_action() const = 0; + virtual ecs_iter_next_action_t next_each_action() const = 0; + + template < template class Delegate, typename Func, typename NextFunc, typename ... Args> + void iterate(flecs::world_t *stage, Func&& func, NextFunc next, Args &&... args) const { + ecs_iter_t it = this->get_iter(stage); + if (Delegate::instanced()) { + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + } + + while (next(&it, FLECS_FWD(args)...)) { + Delegate(func).invoke(&it); + } + } + + template < template class Delegate, typename Func, typename NextFunc, typename ... Args> + flecs::entity iterate_find(flecs::world_t *stage, Func&& func, NextFunc next, Args &&... args) const { + ecs_iter_t it = this->get_iter(stage); + if (Delegate::instanced()) { + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + } + + flecs::entity result; + while (!result && next(&it, FLECS_FWD(args)...)) { + result = Delegate(func).invoke(&it); + } + if (result) { + ecs_iter_fini(&it); + } + return result; + } +}; + +template +struct iter_iterable final : iterable { + template + iter_iterable(Iterable *it, flecs::world_t *world) + { + m_it = it->get_iter(world); + m_next = it->next_action(); + m_next_each = it->next_action(); + } + + iter_iterable& set_var(int var_id, flecs::entity_t value) { + ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); + ecs_iter_set_var(&m_it, var_id, value); + return *this; + } + +# ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/iterable.inl + * @brief Rule iterable mixin. + */ + +/** + * @memberof flecs::iter + * @ingroup cpp_addons_rules + * + * @{ + */ + +iter_iterable& set_var(const char *name, flecs::entity_t value) { + ecs_rule_iter_t *rit = &m_it.priv.iter.rule; + int var_id = ecs_rule_find_var(rit->rule, name); + ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); + ecs_iter_set_var(&m_it, var_id, value); + return *this; +} + +/** @} */ + +# endif +# ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/iterable.inl + * @brief JSON iterable mixin. + */ + +/** Serialize iterator result to JSON. + * + * @memberof flecs::iter + * @ingroup cpp_addons_json + */ +flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { + char *json = ecs_iter_to_json(m_it.real_world, &m_it, desc); + return flecs::string(json); +} + +# endif + + // Return total number of entities in result. + int32_t count() { + int32_t result = 0; + while (m_next_each(&m_it)) { + result += m_it.count; + } + return result; + } + + // Returns true if iterator yields at least once result. + bool is_true() { + bool result = m_next_each(&m_it); + if (result) { + ecs_iter_fini(&m_it); + } + return result; + } + + // Return first matching entity. + flecs::entity first() { + flecs::entity result; + if (m_next_each(&m_it) && m_it.count) { + result = flecs::entity(m_it.world, m_it.entities[0]); + ecs_iter_fini(&m_it); + } + return result; + } + + // Limit results to tables with specified group id (grouped queries only) + iter_iterable& set_group(uint64_t group_id) { + ecs_query_set_group(&m_it, group_id); + return *this; + } + + // Limit results to tables with specified group id (grouped queries only) + template + iter_iterable& set_group() { + ecs_query_set_group(&m_it, _::cpp_type().id(m_it.real_world)); + return *this; + } + +protected: + ecs_iter_t get_iter(flecs::world_t *world) const { + if (world) { + ecs_iter_t result = m_it; + result.world = world; + return result; + } + return m_it; + } + + ecs_iter_next_action_t next_action() const { + return m_next; + } + + ecs_iter_next_action_t next_each_action() const { + return m_next_each; + } + +private: + ecs_iter_t m_it; + ecs_iter_next_action_t m_next; + ecs_iter_next_action_t m_next_each; +}; + +template +iter_iterable iterable::iter(flecs::world_t *world) const +{ + return iter_iterable(this, world); +} + +template +struct page_iterable final : iterable { + template + page_iterable(int32_t offset, int32_t limit, Iterable *it) + : m_offset(offset) + , m_limit(limit) + { + m_chain_it = it->get_iter(nullptr); + } + +protected: + ecs_iter_t get_iter(flecs::world_t*) const { + return ecs_page_iter(&m_chain_it, m_offset, m_limit); + } + + ecs_iter_next_action_t next_action() const { + return ecs_page_next; + } + + ecs_iter_next_action_t next_each_action() const { + return ecs_page_next; + } + +private: + ecs_iter_t m_chain_it; + int32_t m_offset; + int32_t m_limit; +}; + +template +page_iterable iterable::page( + int32_t offset, + int32_t limit) +{ + return page_iterable(offset, limit, this); +} + +template +struct worker_iterable final : iterable { + worker_iterable(int32_t offset, int32_t limit, iterable *it) + : m_offset(offset) + , m_limit(limit) + { + m_chain_it = it->get_iter(nullptr); + } + +protected: + ecs_iter_t get_iter(flecs::world_t*) const { + return ecs_worker_iter(&m_chain_it, m_offset, m_limit); + } + + ecs_iter_next_action_t next_action() const { + return ecs_worker_next; + } + + ecs_iter_next_action_t next_each_action() const { + return ecs_worker_next; + } + +private: + ecs_iter_t m_chain_it; + int32_t m_offset; + int32_t m_limit; +}; + +template +worker_iterable iterable::worker( + int32_t index, + int32_t count) +{ + return worker_iterable(index, count, this); +} + +} + +/** + * @file addons/cpp/component.hpp + * @brief Registering/obtaining info from components. + */ + +#pragma once + +#include +#include + +/** + * @defgroup cpp_components Components + * @ingroup cpp_core + * Registering and working with components. + * + * @{ + */ + +namespace flecs { + +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. The resulting string should be the same on all platforms +// for all compilers. +// + +#if defined(__GNUC__) || defined(_WIN32) +template +inline static const char* type_name() { + static const size_t len = ECS_FUNC_TYPE_LEN(const char*, type_name, ECS_FUNC_NAME); + static char result[len + 1] = {}; + static const size_t front_len = ECS_FUNC_NAME_FRONT(const char*, type_name); + return ecs_cpp_get_type_name(result, ECS_FUNC_NAME, len, front_len); +} +#else +#error "implicit component registration not supported" +#endif + +// Translate a typename into a language-agnostic identifier. This allows for +// registration of components/modules across language boundaries. +template +inline static const char* symbol_name() { + static const size_t len = ECS_FUNC_TYPE_LEN(const char*, symbol_name, ECS_FUNC_NAME); + static char result[len + 1] = {}; + return ecs_cpp_get_symbol_name(result, type_name(), len); +} + +template <> inline const char* symbol_name() { + return "u8"; +} +template <> inline const char* symbol_name() { + return "u16"; +} +template <> inline const char* symbol_name() { + return "u32"; +} +template <> inline const char* symbol_name() { + return "u64"; +} +template <> inline const char* symbol_name() { + return "i8"; +} +template <> inline const char* symbol_name() { + return "i16"; +} +template <> inline const char* symbol_name() { + return "i32"; +} +template <> inline const char* symbol_name() { + return "i64"; +} +template <> inline const char* symbol_name() { + return "f32"; +} +template <> inline const char* symbol_name() { + return "f64"; +} + +// If type is trivial, don't register lifecycle actions. While the functions +// that obtain the lifecycle callback do detect whether the callback is required +// adding a special case for trivial types eases the burden a bit on the +// compiler as it reduces the number of templates to evaluate. +template::value == true + >* = nullptr> +void register_lifecycle_actions(ecs_world_t*, ecs_entity_t) { } + +// If the component is non-trivial, register component lifecycle actions. +// Depending on the type not all callbacks may be available. +template::value == false + >* = nullptr> +void register_lifecycle_actions( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_type_hooks_t cl{}; + cl.ctor = ctor(); + cl.dtor = dtor(); + + cl.copy = copy(); + cl.copy_ctor = copy_ctor(); + cl.move = move(); + cl.move_ctor = move_ctor(); + + cl.ctor_move_dtor = ctor_move_dtor(); + cl.move_dtor = move_dtor(); + + ecs_set_hooks_id( world, component, &cl); +} + +// Class that manages component ids across worlds & binaries. +// The cpp_type class stores the component id for a C++ type in a static global +// variable that is shared between worlds. Whenever a component is used this +// class will check if it already has been registered (has the global id been +// set), and if not, register the component with the world. +// +// If the id has been set, the class will ensure it is known by the world. If it +// is not known the component has been registered by another world and will be +// registered with the world using the same id. If the id does exist, the class +// will register it as a component, and verify whether the input is consistent. +template +struct cpp_type_impl { + // Initialize component identifier + static void init( + entity_t entity, + bool allow_tag = true) + { + if (s_reset_count != ecs_cpp_reset_count_get()) { + reset(); + } + + // If an identifier was already set, check for consistency + if (s_id) { + ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, + type_name()); + ecs_assert(allow_tag == s_allow_tag, ECS_INVALID_PARAMETER, NULL); + + // 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. + s_id = entity; + s_allow_tag = allow_tag; + s_size = sizeof(T); + s_alignment = alignof(T); + if (is_empty::value && allow_tag) { + s_size = 0; + s_alignment = 0; + } + + s_reset_count = ecs_cpp_reset_count_get(); + } + + // Obtain a component identifier for explicit component registration. + static entity_t id_explicit(world_t *world = nullptr, + const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0, + bool is_component = true, bool *existing = nullptr) + { + if (!s_id) { + // If no world was provided the component cannot be registered + ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); + } else { + ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL); + } + + // If no id has been registered yet for the component (indicating the + // component has not yet been registered, or the component is used + // across more than one binary), or if the id does not exists in the + // world (indicating a multi-world application), register it. + if (!s_id || (world && !ecs_exists(world, s_id))) { + init(s_id ? s_id : id, allow_tag); + + ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL); + + const char *symbol = nullptr; + if (id) { + symbol = ecs_get_symbol(world, id); + } + if (!symbol) { + symbol = symbol_name(); + } + + entity_t entity = ecs_cpp_component_register_explicit( + world, s_id, id, name, type_name(), symbol, + s_size, s_alignment, is_component, existing); + + s_id = entity; + + // If component is enum type, register constants + #if FLECS_CPP_ENUM_REFLECTION_SUPPORT + _::init_enum(world, entity); + #endif + } + + // By now the identifier must be valid and known with the world. + ecs_assert(s_id != 0 && ecs_exists(world, s_id), + ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Obtain a component identifier for implicit component registration. This + // is almost the same as id_explicit, except that this operation + // automatically registers lifecycle callbacks. + // Additionally, implicit registration temporarily resets the scope & with + // state of the world, so that the component is not implicitly created with + // the scope/with of the code it happens to be first used by. + static id_t id(world_t *world = nullptr, const char *name = nullptr, + bool allow_tag = true) + { + // If no id has been registered yet, do it now. +#ifndef FLECS_CPP_NO_AUTO_REGISTRATION + if (!registered(world)) { + ecs_entity_t prev_scope = 0; + ecs_id_t prev_with = 0; + + if (world) { + prev_scope = ecs_set_scope(world, 0); + prev_with = ecs_set_with(world, 0); + } + + // This will register a component id, but will not register + // lifecycle callbacks. + bool existing; + id_explicit(world, name, allow_tag, 0, true, &existing); + + // 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() && !existing) { + register_lifecycle_actions(world, s_id); + } + + if (prev_with) { + ecs_set_with(world, prev_with); + } + if (prev_scope) { + ecs_set_scope(world, prev_scope); + } + } +#else + (void)world; + (void)name; + (void)allow_tag; + + ecs_assert(registered(world), ECS_INVALID_OPERATION, + "component '%s' was not registered before use", + type_name()); +#endif + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Return the size of a component. + static size_t size() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return s_size; + } + + // Return the alignment of a component. + static size_t alignment() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return s_alignment; + } + + // Was the component already registered. + static bool registered(flecs::world_t *world) { + if (s_reset_count != ecs_cpp_reset_count_get()) { + reset(); + } + if (s_id == 0) { + return false; + } + if (world && !ecs_exists(world, s_id)) { + return false; + } + return true; + } + + // 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_size = 0; + s_alignment = 0; + s_allow_tag = true; + } + + static entity_t s_id; + static size_t s_size; + static size_t s_alignment; + static bool s_allow_tag; + static int32_t s_reset_count; +}; + +// Global templated variables that hold component identifier and other info +template entity_t cpp_type_impl::s_id; +template size_t cpp_type_impl::s_size; +template size_t cpp_type_impl::s_alignment; +template bool cpp_type_impl::s_allow_tag( true ); +template int32_t cpp_type_impl::s_reset_count; + +// Front facing class for implicitly registering a component & obtaining +// static component data + +// Regular type +template +struct cpp_type::value >> + : cpp_type_impl> { }; + +// Pair type +template +struct cpp_type::value >> +{ + // Override id method to return id of pair + static id_t id(world_t *world = nullptr) { + return ecs_pair( + cpp_type< pair_first_t >::id(world), + cpp_type< pair_second_t >::id(world)); + } +}; + +} // namespace _ + +/** Untyped component class. + * Generic base class for flecs::component. + * + * @ingroup cpp_components + */ +struct untyped_component : entity { + using entity::entity; + +# ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/untyped_component.inl + * @brief Meta component mixin. + */ + +/** + * @memberof flecs::component + * @ingroup cpp_addons_meta + * + * @{ + */ + +/** Add member with unit. */ +untyped_component& member(flecs::entity_t type_id, flecs::entity_t unit, const char *name, int32_t count = 0, size_t offset = 0) { + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.add[0] = ecs_pair(flecs::ChildOf, m_id); + ecs_entity_t eid = ecs_entity_init(m_world, &desc); + ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); + + flecs::entity e(m_world, eid); + + Member m = {}; + m.type = type_id; + m.unit = unit; + m.count = count; + m.offset = static_cast(offset); + e.set(m); + + return *this; +} + +/** Add member. */ +untyped_component& member(flecs::entity_t type_id, const char* name, int32_t count = 0, size_t offset = 0) { + return member(type_id, 0, name, count, offset); +} + +/** Add member. */ +template +untyped_component& member(const char *name, int32_t count = 0, size_t offset = 0) { + flecs::entity_t type_id = _::cpp_type::id(m_world); + return member(type_id, name, count, offset); +} + +/** Add member with unit. */ +template +untyped_component& member(flecs::entity_t unit, const char *name, int32_t count = 0, size_t offset = 0) { + flecs::entity_t type_id = _::cpp_type::id(m_world); + return member(type_id, unit, name, count, offset); +} + +/** Add member with unit. */ +template +untyped_component& member(const char *name, int32_t count = 0, size_t offset = 0) { + flecs::entity_t type_id = _::cpp_type::id(m_world); + flecs::entity_t unit_id = _::cpp_type::id(m_world); + return member(type_id, unit_id, name, count, offset); +} + +/** Add member using pointer-to-member. */ +template ::type> +untyped_component& member(const char* name, const MemberType ComponentType::* ptr) { + flecs::entity_t type_id = _::cpp_type::id(m_world); + size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); + return member(type_id, name, std::extent::value, offset); +} + +/** Add member with unit using pointer-to-member. */ +template ::type> +untyped_component& member(flecs::entity_t unit, const char* name, const MemberType ComponentType::* ptr) { + flecs::entity_t type_id = _::cpp_type::id(m_world); + size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); + return member(type_id, unit, name, std::extent::value, offset); +} + +/** Add member with unit using pointer-to-member. */ +template ::type> +untyped_component& member(const char* name, const MemberType ComponentType::* ptr) { + flecs::entity_t type_id = _::cpp_type::id(m_world); + flecs::entity_t unit_id = _::cpp_type::id(m_world); + size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); + return member(type_id, unit_id, name, std::extent::value, offset); +} + +/** Add constant. */ +untyped_component& constant(const char *name, int32_t value) { + ecs_add_id(m_world, m_id, _::cpp_type::id(m_world)); + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.add[0] = ecs_pair(flecs::ChildOf, m_id); + ecs_entity_t eid = ecs_entity_init(m_world, &desc); + ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_set_id(m_world, eid, + ecs_pair(flecs::Constant, flecs::I32), sizeof(int32_t), + &value); + + return *this; +} + +/** Add bitmask constant. */ +untyped_component& bit(const char *name, uint32_t value) { + ecs_add_id(m_world, m_id, _::cpp_type::id(m_world)); + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.add[0] = ecs_pair(flecs::ChildOf, m_id); + ecs_entity_t eid = ecs_entity_init(m_world, &desc); + ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_set_id(m_world, eid, + ecs_pair(flecs::Constant, flecs::U32), sizeof(uint32_t), + &value); + + return *this; +} + +/** Register array metadata for component */ +template +untyped_component& array(int32_t elem_count) { + ecs_array_desc_t desc = {}; + desc.entity = m_id; + desc.type = _::cpp_type::id(m_world); + desc.count = elem_count; + ecs_array_init(m_world, &desc); + return *this; +} + +/** Add member value range */ +untyped_component& range(double min, double max) { + const flecs::member_t *m = ecs_cpp_last_member(m_world, m_id); + if (!m) { + return *this; + } + + flecs::world w(m_world); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = me.get_mut(); + mr->value.min = min; + mr->value.max = max; + me.modified(); + return *this; +} + +/** Add member warning range */ +untyped_component& warning_range(double min, double max) { + const flecs::member_t *m = ecs_cpp_last_member(m_world, m_id); + if (!m) { + return *this; + } + + flecs::world w(m_world); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = me.get_mut(); + mr->warning.min = min; + mr->warning.max = max; + me.modified(); + return *this; +} + +/** Add member error range */ +untyped_component& error_range(double min, double max) { + const flecs::member_t *m = ecs_cpp_last_member(m_world, m_id); + if (!m) { + return *this; + } + + flecs::world w(m_world); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = me.get_mut(); + mr->error.min = min; + mr->error.max = max; + me.modified(); + return *this; +} + + +/** @} */ + +# endif +# ifdef FLECS_METRICS +/** + * @file addons/cpp/mixins/meta/untyped_component.inl + * @brief Metrics component mixin. + */ + +/** + * @memberof flecs::component + * @ingroup cpp_addons_metrics + * + * @{ + */ + +/** Register member as metric. + * When no explicit name is provided, this operation will derive the metric name + * from the member name. When the member name is "value", the operation will use + * the name of the component. + * + * When the brief parameter is provided, it is set on the metric as if + * set_doc_brief is used. The brief description can be obtained with + * get_doc_brief. + * + * @tparam Kind Metric kind (Counter, CounterIncrement or Gauge). + * @param parent Parent entity of the metric (optional). + * @param brief Description for metric (optional). + * @param name Name of metric (optional). + */ +template +untyped_component& metric( + flecs::entity_t parent = 0, + const char *brief = nullptr, + const char *name = nullptr); + +/** @} */ + +# endif +}; + +/** Component class. + * Class used to register components and component metadata. + * + * @ingroup cpp_components + */ +template +struct component : untyped_component { + /** Register a component. + * If the component was already registered, this operation will return a handle + * to the existing component. + * + * @param world The world for which to register the component. + * @param name Optional name (overrides typename). + * @param allow_tag If true, empty types will be registered with size 0. + * @param id Optional id to register component with. + */ + component( + flecs::world_t *world, + const char *name = nullptr, + bool allow_tag = true, + flecs::id_t id = 0) + { + const char *n = name; + bool implicit_name = false; + if (!n) { + n = _::type_name(); + + /* Keep track of whether name was explicitly set. If not, and the + * component was already registered, just use the registered name. + * + * The registered name may differ from the typename as the registered + * name includes the flecs scope. This can in theory be different from + * the C++ namespace though it is good practice to keep them the same */ + implicit_name = true; + } + + if (_::cpp_type::registered(world)) { + /* Obtain component id. Because the component is already registered, + * this operation does nothing besides returning the existing id */ + id = _::cpp_type::id_explicit(world, name, allow_tag, id); + + ecs_cpp_component_validate(world, id, n, _::symbol_name(), + _::cpp_type::size(), + _::cpp_type::alignment(), + implicit_name); + } else { + /* If component is registered from an existing scope, ignore the + * namespace in the name of the component. */ + if (implicit_name && (ecs_get_scope(world) != 0)) { + /* If the type is a template type, make sure to ignore ':' + * inside the template parameter list. */ + const char *start = strchr(n, '<'), *last_elem = NULL; + if (start) { + const char *ptr = start; + while (ptr[0] && (ptr[0] != ':') && (ptr > n)) { + ptr --; + } + if (ptr[0] == ':') { + last_elem = ptr; + } + } + if (last_elem) { + name = last_elem + 1; + } + } + + /* Find or register component */ + bool existing; + id = ecs_cpp_component_register(world, id, n, _::symbol_name(), + ECS_SIZEOF(T), ECS_ALIGNOF(T), implicit_name, &existing); + + /* Initialize static component data */ + id = _::cpp_type::id_explicit(world, name, allow_tag, id); + + /* Initialize lifecycle actions (ctor, dtor, copy, move) */ + if (_::cpp_type::size() && !existing) { + _::register_lifecycle_actions(world, id); + } + } + + m_world = world; + m_id = id; + } + + /** Register on_add hook. */ + template + component& on_add(Func&& func) { + using Delegate = typename _::each_delegate::type, T>; + flecs::type_hooks_t h = get_hooks(); + ecs_assert(h.on_add == nullptr, ECS_INVALID_OPERATION, + "on_add hook is already set"); + BindingCtx *ctx = get_binding_ctx(h); + h.on_add = Delegate::run_add; + ctx->on_add = FLECS_NEW(Delegate)(FLECS_FWD(func)); + ctx->free_on_add = reinterpret_cast( + _::free_obj); + ecs_set_hooks_id(m_world, m_id, &h); + return *this; + } + + /** Register on_remove hook. */ + template + component& on_remove(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, T>; + flecs::type_hooks_t h = get_hooks(); + ecs_assert(h.on_remove == nullptr, ECS_INVALID_OPERATION, + "on_remove hook is already set"); + BindingCtx *ctx = get_binding_ctx(h); + h.on_remove = Delegate::run_remove; + ctx->on_remove = FLECS_NEW(Delegate)(FLECS_FWD(func)); + ctx->free_on_remove = reinterpret_cast( + _::free_obj); + ecs_set_hooks_id(m_world, m_id, &h); + return *this; + } + + /** Register on_set hook. */ + template + component& on_set(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, T>; + flecs::type_hooks_t h = get_hooks(); + ecs_assert(h.on_set == nullptr, ECS_INVALID_OPERATION, + "on_set hook is already set"); + BindingCtx *ctx = get_binding_ctx(h); + h.on_set = Delegate::run_set; + ctx->on_set = FLECS_NEW(Delegate)(FLECS_FWD(func)); + ctx->free_on_set = reinterpret_cast( + _::free_obj); + ecs_set_hooks_id(m_world, m_id, &h); + return *this; + } + +# ifdef FLECS_META + +/** Register opaque type interface */ +template +component& opaque(const Func& type_support) { + flecs::world world(m_world); + auto ts = type_support(world); + ts.desc.entity = _::cpp_type::id(m_world); + ecs_opaque_init(m_world, &ts.desc); + return *this; +} + +flecs::opaque opaque(flecs::entity_t as_type) { + return flecs::opaque(m_world).as_type(as_type); +} + +flecs::opaque opaque(flecs::entity as_type) { + return this->opaque(as_type.id()); +} + +flecs::opaque opaque(flecs::untyped_component as_type) { + return this->opaque(as_type.id()); +} + +/** Return opaque type builder for collection type */ +template +flecs::opaque opaque(flecs::id_t as_type) { + return flecs::opaque(m_world).as_type(as_type); +} + +/** Add constant. */ +component& constant(const char *name, T value) { + int32_t v = static_cast(value); + untyped_component::constant(name, v); + return *this; +} + +# endif + +private: + using BindingCtx = _::component_binding_ctx; + + BindingCtx* get_binding_ctx(flecs::type_hooks_t& h){ + BindingCtx *result = static_cast(h.binding_ctx); + if (!result) { + result = FLECS_NEW(BindingCtx); + h.binding_ctx = result; + h.binding_ctx_free = reinterpret_cast( + _::free_obj); + } + return result; + } + + flecs::type_hooks_t get_hooks() { + const flecs::type_hooks_t* h = ecs_get_hooks_id(m_world, m_id); + if (h) { + return *h; + } else { + return {}; + } + } +}; + +/** Get id currently assigned to component. If no world has registered the + * component yet, this operation will return 0. */ +template +flecs::entity_t type_id() { + if (_::cpp_type::s_reset_count == ecs_cpp_reset_count_get()) { + return _::cpp_type::s_id; + } else { + return 0; + } +} + +/** Reset static component ids. + * When components are registered their component ids are stored in a static + * type specific variable. This stored id is passed into component registration + * functions to ensure consistent ids across worlds. + * + * In some cases this can be undesirable, like when a process repeatedly creates + * worlds with different components. A typical example where this can happen is + * when running multiple tests in a single process, where each test registers + * its own set of components. + * + * This operation can be used to prevent reusing of component ids and force + * generating a new ids upon registration. + * + * Note that this operation should *never* be called while there are still + * alive worlds in a process. Doing so results in undefined behavior. + * + * Also note that this operation does not actually change the static component + * variables. It only ensures that the next time a component id is requested, a + * new id will be generated. + * + * @ingroup cpp_components + */ +inline void reset() { + ecs_cpp_reset_count_inc(); +} + +} + +/** @} */ + +/** + * @file addons/cpp/type.hpp + * @brief Utility functions for id vector. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_types Types + * @ingroup cpp_core + * @brief Type operations. + * + * @{ + */ + +/** Type class. + * A type is a vector of component ids which can be requested from entities or tables. + */ +struct type { + type() : m_world(nullptr), m_type(nullptr) { } + + type(world_t *world, const type_t *t) + : m_world(world) + , m_type(t) { } + + /** Convert type to comma-separated string */ + flecs::string str() const { + return flecs::string(ecs_type_str(m_world, m_type)); + } + + /** Return number of ids in type */ + int32_t count() const { + if (!m_type) { + return 0; + } + return m_type->count; + } + + /** Return pointer to array. */ + flecs::id_t* array() const { + if (!m_type) { + return nullptr; + } + return m_type->array; + } + + /** Get id at specified index in type */ + flecs::id get(int32_t index) const { + ecs_assert(m_type != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_type->count > index, ECS_OUT_OF_RANGE, NULL); + if (!m_type) { + return flecs::id(); + } + return flecs::id(m_world, m_type->array[index]); + } + + flecs::id_t* begin() const { + return m_type->array; + } + + flecs::id_t* end() const { + return &m_type->array[m_type->count]; + } + + /** Implicit conversion to type_t */ + operator const type_t*() const { + return m_type; + } +private: + world_t *m_world; + const type_t *m_type; +}; + +/** #} */ + +} + +/** + * @file addons/cpp/table.hpp + * @brief Direct access to table data. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_tables Tables + * @ingroup cpp_core + * Table operations. + * + * @{ + */ + +struct table { + table() : m_world(nullptr), m_table(nullptr) { } + + table(world_t *world, table_t *t) + : m_world(world) + , m_table(t) { } + + virtual ~table() { } + + /** Convert table type to string. */ + flecs::string str() const { + return flecs::string(ecs_table_str(m_world, m_table)); + } + + /** Get table type. */ + flecs::type type() const { + return flecs::type(m_world, ecs_table_get_type(m_table)); + } + + /** Get table count. */ + int32_t count() const { + return ecs_table_count(m_table); + } + + /** Find type index for (component) id. + * + * @param id The (component) id. + * @return The index of the id in the table type, -1 if not found/ + */ + int32_t type_index(flecs::id_t id) const { + return ecs_table_get_type_index(m_world, m_table, id); + } + + /** Find type index for type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ + template + int32_t type_index() const { + return type_index(_::cpp_type::id(m_world)); + } + + /** Find type index for pair. + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + int32_t type_index(flecs::entity_t first, flecs::entity_t second) const { + return type_index(ecs_pair(first, second)); + } + + /** Find type index for pair. + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t type_index(flecs::entity_t second) const { + return type_index(_::cpp_type::id(m_world), second); + } + + /** Find type index for pair. + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t type_index() const { + return type_index(_::cpp_type::id(m_world)); + } + + /** Find column index for (component) id. + * + * @param id The (component) id. + * @return The index of the id in the table type, -1 if not found/ + */ + int32_t column_index(flecs::id_t id) const { + return ecs_table_get_column_index(m_world, m_table, id); + } + + /** Find column index for type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ + template + int32_t column_index() const { + return column_index(_::cpp_type::id(m_world)); + } + + /** Find column index for pair. + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + int32_t column_index(flecs::entity_t first, flecs::entity_t second) const { + return column_index(ecs_pair(first, second)); + } + + /** Find column index for pair. + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t column_index(flecs::entity_t second) const { + return column_index(_::cpp_type::id(m_world), second); + } + + /** Find column index for pair. + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t column_index() const { + return column_index(_::cpp_type::id(m_world)); + } + + /** Test if table has (component) id. + * + * @param id The (component) id. + * @return True if the table has the id, false if not. + */ + bool has(flecs::id_t id) const { + return type_index(id) != -1; + } + + /** Test if table has the type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ + template + bool has() const { + return type_index() != -1; + } + + /** Test if table has the pair. + * + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + bool has(flecs::entity_t first, flecs::entity_t second) const { + return type_index(first, second) != -1; + } + + /** Test if table has the pair. + * + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + bool has(flecs::entity_t second) const { + return type_index(second) != -1; + } + + /** Test if table has the pair. + * + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + bool has() const { + return type_index() != -1; + } + + /** Get pointer to component array by column index. + * + * @param index The column index. + * @return Pointer to the column, NULL if not a component. + */ + virtual void* get_column(int32_t index) const { + return ecs_table_get_column(m_table, index, 0); + } + + /** Get pointer to component array by component. + * + * @param id The component id. + * @return Pointer to the column, NULL if not found. + */ + void* get(flecs::id_t id) const { + int32_t index = column_index(id); + if (index == -1) { + return NULL; + } + return get_column(index); + } + + /** Get pointer to component array by pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + void* get(flecs::entity_t first, flecs::entity_t second) const { + return get(ecs_pair(first, second)); + } + + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template ::value > = 0> + T* get() const { + return static_cast(get(_::cpp_type::id(m_world))); + } + + /** Get pointer to component array by (enum) component. + * + * @tparam T The (enum) component. + * @return Pointer to the column, NULL if not found. + */ + template ::value > = 0> + T* get() const { + return static_cast(get(_::cpp_type::id(m_world))); + } + + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template , + if_t< flecs::is_pair::value > = 0> + A* get() const { + return static_cast(get(_::cpp_type::id(m_world))); + } + + /** Get pointer to component array by pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + template + First* get(flecs::entity_t second) const { + return static_cast(get(_::cpp_type::id(m_world), second)); + } + + /** Get pointer to component array by pair. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + A* get() const { + return static_cast(get(_::cpp_type::id(m_world))); + } + + /** Get column size */ + size_t column_size(int32_t index) { + return ecs_table_get_column_size(m_table, index); + } + + /** Get depth for given relationship. + * + * @param rel The relationship. + * @return The depth. + */ + int32_t depth(flecs::entity_t rel) { + return ecs_table_get_depth(m_world, m_table, rel); + } + + /** Get depth for given relationship. + * + * @tparam Rel The relationship. + * @return The depth. + */ + template + int32_t depth() { + return depth(_::cpp_type::id(m_world)); + } + + /* Implicit conversion to table_t */ + operator table_t*() const { + return m_table; + } + +protected: + world_t *m_world; + table_t *m_table; +}; + +struct table_range : table { + table_range() + : table() + , m_offset(0) + , m_count(0) { } + + table_range(world_t *world, table_t *t, int32_t offset, int32_t count) + : table(world, t) + , m_offset(offset) + , m_count(count) { } + + int32_t offset() const { + return m_offset; + } + + int32_t count() const { + return m_count; + } + + /** Get pointer to component array by column index. + * + * @param index The column index. + * @return Pointer to the column, NULL if not a component. + */ + void* get_column(int32_t index) const override { + return ecs_table_get_column(m_table, index, m_offset); + } + +private: + int32_t m_offset = 0; + int32_t m_count = 0; +}; + +/** @} */ + +} + + +// Mixin implementations +/** + * @file addons/cpp/mixins/id/impl.hpp + * @brief Id class implementation. + */ + +#pragma once + +namespace flecs { + +inline flecs::entity id::entity() const { + ecs_assert(!is_pair(), ECS_INVALID_OPERATION, NULL); + ecs_assert(!flags(), ECS_INVALID_OPERATION, NULL); + return flecs::entity(m_world, m_id); +} + +inline flecs::entity id::flags() const { + return flecs::entity(m_world, m_id & ECS_ID_FLAGS_MASK); +} + +inline flecs::entity id::first() const { + ecs_assert(is_pair(), ECS_INVALID_OPERATION, NULL); + + flecs::entity_t e = ECS_PAIR_FIRST(m_id); + if (m_world) { + return flecs::entity(m_world, ecs_get_alive(m_world, e)); + } else { + return flecs::entity(e); + } +} + +inline flecs::entity id::second() const { + flecs::entity_t e = ECS_PAIR_SECOND(m_id); + if (m_world) { + return flecs::entity(m_world, ecs_get_alive(m_world, e)); + } else { + return flecs::entity(e); + } +} + +inline flecs::entity id::add_flags(flecs::id_t flags) const { + return flecs::entity(m_world, m_id | flags); +} + +inline flecs::entity id::remove_flags(flecs::id_t flags) const { + (void)flags; + ecs_assert((m_id & ECS_ID_FLAGS_MASK) == flags, ECS_INVALID_PARAMETER, NULL); + return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_flags() const { + return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_generation() const { + return flecs::entity(m_world, static_cast(m_id)); +} + +inline flecs::world id::world() const { + return flecs::world(m_world); +} + +inline flecs::entity id::type_id() const { + return flecs::entity(m_world, ecs_get_typeid(m_world, m_id)); +} + + +// Id mixin implementation + +template +inline flecs::id world::id() const { + return flecs::id(m_world, _::cpp_type::id(m_world)); +} + +template +inline flecs::id world::id(Args&&... args) const { + return flecs::id(m_world, FLECS_FWD(args)...); +} + +template +inline flecs::id world::pair() const { + return flecs::id( + m_world, + ecs_pair( + _::cpp_type::id(m_world), + _::cpp_type::id(m_world))); +} + +template +inline flecs::id world::pair(entity_t o) const { + ecs_assert(!ECS_IS_PAIR(o), ECS_INVALID_PARAMETER, + "cannot create nested pairs"); + + return flecs::id( + m_world, + ecs_pair( + _::cpp_type::id(m_world), + o)); +} + +inline flecs::id world::pair(entity_t r, entity_t o) const { + ecs_assert(!ECS_IS_PAIR(r) && !ECS_IS_PAIR(o), ECS_INVALID_PARAMETER, + "cannot create nested pairs"); + + return flecs::id( + m_world, + ecs_pair(r, o)); +} + +} + +/** + * @file addons/cpp/mixins/entity/impl.hpp + * @brief Entity implementation. + */ + +#pragma once + +namespace flecs { + +template +flecs::entity ref::entity() const { + return flecs::entity(m_world, m_ref.entity); +} + +template +template ::value > > +inline Self& entity_builder::set(const Func& func) { + _::entity_with_delegate::invoke_get_mut( + this->m_world, this->m_id, func); + return to_base(); +} + +template ::value > > +const T* entity_view::get() const { + entity_t r = _::cpp_type::id(m_world); + entity_t c = ecs_get_target(m_world, m_id, r, 0); + + if (c) { + // Get constant value from constant entity + const T* v = static_cast(ecs_get_id(m_world, c, r)); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, + "missing enum constant value"); + return v; + } else { + // If there is no matching pair for (r, *), try just r + return static_cast(ecs_get_id(m_world, m_id, r)); + } +} + +template +inline flecs::entity entity_view::target(int32_t index) const +{ + return flecs::entity(m_world, + ecs_get_target(m_world, m_id, _::cpp_type::id(m_world), index)); +} + +inline flecs::entity entity_view::target( + flecs::entity_t relationship, + int32_t index) const +{ + return flecs::entity(m_world, + ecs_get_target(m_world, m_id, relationship, index)); +} + +inline flecs::entity entity_view::target_for( + flecs::entity_t relationship, + flecs::id_t id) const +{ + return flecs::entity(m_world, + ecs_get_target_for_id(m_world, m_id, relationship, id)); +} + +template +inline flecs::entity entity_view::target_for(flecs::entity_t relationship) const { + return target_for(relationship, _::cpp_type::id(m_world)); +} + +template +inline flecs::entity entity_view::target_for(flecs::entity_t relationship) const { + return target_for(relationship, _::cpp_type::id(m_world)); +} + +inline flecs::entity entity_view::parent() const { + return target(flecs::ChildOf); +} + +inline flecs::entity entity_view::mut(const flecs::world& stage) const { + ecs_assert(!stage.is_readonly(), ECS_INVALID_PARAMETER, + "cannot use readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(stage.c_ptr()); +} + +inline flecs::entity entity_view::mut(const flecs::iter& it) const { + ecs_assert(!it.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use iterator created for readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(it.world().c_ptr()); +} + +inline flecs::entity entity_view::mut(const flecs::entity_view& e) const { + ecs_assert(!e.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use entity created for readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(e.m_world); +} + +inline flecs::entity entity_view::set_stage(world_t *stage) { + return flecs::entity(stage, m_id); +} + +inline flecs::type entity_view::type() const { + return flecs::type(m_world, ecs_get_type(m_world, m_id)); +} + +inline flecs::table entity_view::table() const { + return flecs::table(m_world, ecs_get_table(m_world, m_id)); +} + +inline flecs::table_range entity_view::range() const { + ecs_record_t *r = ecs_record_find(m_world, m_id); + if (r) { + return flecs::table_range(m_world, r->table, + ECS_RECORD_TO_ROW(r->row), 1); + } + return flecs::table_range(); +} + +template +inline void entity_view::each(const Func& func) const { + const ecs_type_t *type = ecs_get_type(m_world, m_id); + if (!type) { + return; + } + + const ecs_id_t *ids = type->array; + int32_t count = type->count; + + for (int i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + flecs::id ent(m_world, id); + func(ent); + + // Union object is not stored in type, so handle separately + if (ECS_PAIR_FIRST(id) == EcsUnion) { + ent = flecs::id(m_world, ECS_PAIR_SECOND(id), + ecs_get_target(m_world, m_id, ECS_PAIR_SECOND(id), 0)); + func(ent); + } + } +} + +template +inline void entity_view::each(flecs::id_t pred, flecs::id_t obj, const Func& func) const { + flecs::world_t *real_world = const_cast( + ecs_get_world(m_world)); + + const ecs_table_t *table = ecs_get_table(m_world, m_id); + if (!table) { + return; + } + + const ecs_type_t *type = ecs_table_get_type(table); + if (!type) { + return; + } + + flecs::id_t pattern = pred; + if (obj) { + pattern = ecs_pair(pred, obj); + } + + int32_t cur = 0; + id_t *ids = type->array; + + while (-1 != (cur = ecs_search_offset(real_world, table, cur, pattern, 0))) + { + flecs::id ent(m_world, ids[cur]); + func(ent); + cur ++; + } +} + +template +inline void entity_view::each(const flecs::entity_view& rel, const Func& func) const { + return this->each(rel, flecs::Wildcard, [&](flecs::id id) { + flecs::entity obj = id.second(); + func(obj); + }); +} + +template ::value > > +inline bool entity_view::get(const Func& func) const { + return _::entity_with_delegate::invoke_get(m_world, m_id, func); +} + +inline flecs::entity entity_view::lookup(const char *path, bool search_path) const { + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, "invalid lookup from null handle"); + auto id = ecs_lookup_path_w_sep(m_world, m_id, path, "::", "::", search_path); + return flecs::entity(m_world, id); +} + +inline flecs::entity entity_view::clone(bool copy_value, flecs::entity_t dst_id) const { + if (!dst_id) { + dst_id = ecs_new_id(m_world); + } + + flecs::entity dst = flecs::entity(m_world, dst_id); + ecs_clone(m_world, dst_id, m_id, copy_value); + return dst; +} + +// Entity mixin implementation +template +inline flecs::entity world::entity(Args &&... args) const { + return flecs::entity(m_world, FLECS_FWD(args)...); +} + +template ::value >> +inline flecs::id world::id(E value) const { + flecs::entity_t constant = enum_type(m_world).entity(value); + return flecs::id(m_world, constant); +} + +template ::value >> +inline flecs::entity world::entity(E value) const { + flecs::entity_t constant = enum_type(m_world).entity(value); + return flecs::entity(m_world, constant); +} + +template +inline flecs::entity world::entity(const char *name) const { + return flecs::entity(m_world, + _::cpp_type::id_explicit(m_world, name, true, 0, false) ); +} + +template +inline flecs::entity world::prefab(Args &&... args) const { + flecs::entity result = flecs::entity(m_world, FLECS_FWD(args)...); + result.add(flecs::Prefab); + return result; +} + +template +inline flecs::entity world::prefab(const char *name) const { + flecs::entity result = flecs::component(m_world, name, true); + result.add(flecs::Prefab); + return result; +} + +} + +/** + * @file addons/cpp/mixins/component/impl.hpp + * @brief Component mixin implementation + */ + +#pragma once + +namespace flecs { + +template +inline flecs::component world::component(Args &&... args) const { + return flecs::component(m_world, FLECS_FWD(args)...); +} + +template +inline flecs::untyped_component world::component(Args &&... args) const { + return flecs::untyped_component(m_world, FLECS_FWD(args)...); +} + +} // namespace flecs + +/** + * @file addons/cpp/mixins/term/impl.hpp + * @brief Term implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/term/builder_i.hpp + * @brief Term builder interface. + */ + +#pragma once + +/** + * @file addons/cpp/utils/signature.hpp + * @brief Compile time utilities for deriving query attributes from param pack. + */ + +#pragma once + +#include + +namespace flecs { +namespace _ { + + template ::value > = 0> + static constexpr flecs::inout_kind_t type_to_inout() { + return flecs::In; + } + + template ::value > = 0> + static constexpr flecs::inout_kind_t type_to_inout() { + return flecs::Out; + } + + template ::value || is_reference::value > = 0> + static constexpr flecs::inout_kind_t type_to_inout() { + return flecs::InOutDefault; + } + + template ::value > = 0> + static constexpr flecs::oper_kind_t type_to_oper() { + return flecs::Optional; + } + + template ::value > = 0> + static constexpr flecs::oper_kind_t type_to_oper() { + return flecs::And; + } + + template + struct sig { + sig(flecs::world_t *world) + : m_world(world) + , ids({ (_::cpp_type::id(world))... }) + , inout ({ (type_to_inout())... }) + , oper ({ (type_to_oper())... }) + { } + + flecs::world_t *m_world; + flecs::array ids; + flecs::array inout; + flecs::array oper; + + template + void populate(const Builder& b) { + size_t i = 0; + for (auto id : ids) { + if (!(id & ECS_ID_FLAGS_MASK)) { + const flecs::type_info_t *ti = ecs_get_type_info(m_world, id); + if (ti) { + // Union relationships always return a value of type + // flecs::entity_t which holds the target id of the + // union relationship. + // If a union component with a non-zero size (like an + // enum) is added to the query signature, the each/iter + // functions would accept a parameter of the component + // type instead of flecs::entity_t, which would cause + // an assert. + ecs_assert(!ti->size || !ecs_has_id(m_world, id, flecs::Union), + ECS_INVALID_PARAMETER, + "use term() method to add union relationship"); + } + } + b->term(id).inout(inout[i]).oper(oper[i]); + i ++; + } + } + }; + +} // namespace _ +} // namespace flecs + + +namespace flecs +{ + +/** Term identifier builder. + * A term identifier describes a single identifier in a term. Identifier + * descriptions can reference entities by id, name or by variable, which means + * the entity will be resolved when the term is evaluated. + * + * @ingroup cpp_core_filters + */ +template +struct term_id_builder_i { + term_id_builder_i() : m_term_id(nullptr) { } + + virtual ~term_id_builder_i() { } + + /* The self flag indicates the term identifier itself is used */ + Base& self() { + this->assert_term_id(); + m_term_id->flags |= flecs::Self; + return *this; + } + + /* The up flag indicates that the term identifier may be substituted by + * traversing a relationship upwards. For example: substitute the identifier + * with its parent by traversing the ChildOf relationship. */ + Base& up(flecs::entity_t trav = 0) { + this->assert_term_id(); + m_term_id->flags |= flecs::Up; + if (trav) { + m_term_id->trav = trav; + } + return *this; + } + + template + Base& up() { + return this->up(_::cpp_type::id(this->world_v())); + } + + /* The cascade flag is like up, but returns results in breadth-first order. + * Only supported for flecs::query */ + Base& cascade(flecs::entity_t trav = 0) { + this->assert_term_id(); + m_term_id->flags |= flecs::Cascade; + if (trav) { + m_term_id->trav = trav; + } + return *this; + } + + template + Base& cascade() { + return this->cascade(_::cpp_type::id(this->world_v())); + } + + /* Use with cascade to iterate results in descending (bottom -> top) order */ + Base& desc() { + this->assert_term_id(); + m_term_id->flags |= flecs::Desc; + return *this; + } + + /* The parent flag is short for up(flecs::ChildOf) */ + Base& parent() { + this->assert_term_id(); + m_term_id->flags |= flecs::Parent; + return *this; + } + + /* Specify relationship to traverse, and flags to indicate direction */ + Base& trav(flecs::entity_t trav, flecs::flags32_t flags = 0) { + this->assert_term_id(); + m_term_id->trav = trav; + m_term_id->flags |= flags; + return *this; + } + + /* Specify value of identifier by id */ + Base& id(flecs::entity_t id) { + this->assert_term_id(); + m_term_id->id = id; + return *this; + } + + /* Specify value of identifier by id. Almost the same as id(entity), but this + * operation explicitly sets the flecs::IsEntity flag. This forces the id to + * be interpreted as entity, whereas not setting the flag would implicitly + * convert ids for builtin variables such as flecs::This to a variable. + * + * This function can also be used to disambiguate id(0), which would match + * both id(entity_t) and id(const char*). + */ + Base& entity(flecs::entity_t entity) { + this->assert_term_id(); + m_term_id->flags = flecs::IsEntity; + m_term_id->id = entity; + return *this; + } + + /* Specify value of identifier by name */ + Base& name(const char *name) { + this->assert_term_id(); + m_term_id->flags |= flecs::IsEntity; + m_term_id->name = const_cast(name); + return *this; + } + + /* Specify identifier is a variable (resolved at query evaluation time) */ + Base& var(const char *var_name) { + this->assert_term_id(); + m_term_id->flags |= flecs::IsVariable; + m_term_id->name = const_cast(var_name); + return *this; + } + + /* Override term id flags */ + Base& flags(flecs::flags32_t flags) { + this->assert_term_id(); + m_term_id->flags = flags; + return *this; + } + + ecs_term_id_t *m_term_id; + +protected: + virtual flecs::world_t* world_v() = 0; + +private: + void assert_term_id() { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, + "no active term (call .term() first)"); + } + + operator Base&() { + return *static_cast(this); + } +}; + +/** Term builder interface. + * A term is a single element of a query expression. + * + * @ingroup cpp_core_filters + */ +template +struct term_builder_i : term_id_builder_i { + term_builder_i() : m_term(nullptr) { } + + term_builder_i(ecs_term_t *term_ptr) { + set_term(term_ptr); + } + + Base& term(id_t id) { + return this->id(id); + } + + /* Call prior to setting values for src identifier */ + Base& src() { + this->assert_term(); + this->m_term_id = &m_term->src; + return *this; + } + + /* Call prior to setting values for first identifier. This is either the + * component identifier, or first element of a pair (in case second is + * populated as well). */ + Base& first() { + this->assert_term(); + this->m_term_id = &m_term->first; + return *this; + } + + /* Call prior to setting values for second identifier. This is the second + * element of a pair. Requires that first() is populated as well. */ + Base& second() { + this->assert_term(); + this->m_term_id = &m_term->second; + return *this; + } + + /* Select src identifier, initialize it with entity id */ + Base& src(flecs::entity_t id) { + this->src(); + this->id(id); + return *this; + } + + /* Select src identifier, initialize it with id associated with type */ + template + Base& src() { + this->src(_::cpp_type::id(this->world_v())); + return *this; + } + + /* Select src identifier, initialize it with name. If name starts with a $ + * the name is interpreted as a variable. */ + Base& src(const char *name) { + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + this->src(); + if (name[0] == '$') { + this->var(&name[1]); + } else { + this->name(name); + } + return *this; + } + + /* Select first identifier, initialize it with entity id */ + Base& first(flecs::entity_t id) { + this->first(); + this->id(id); + return *this; + } + + /* Select first identifier, initialize it with id associated with type */ + template + Base& first() { + this->first(_::cpp_type::id(this->world_v())); + return *this; + } + + /* Select first identifier, initialize it with name. If name starts with a $ + * the name is interpreted as a variable. */ + Base& first(const char *name) { + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + this->first(); + if (name[0] == '$') { + this->var(&name[1]); + } else { + this->name(name); + } + return *this; + } + + /* Select second identifier, initialize it with entity id */ + Base& second(flecs::entity_t id) { + this->second(); + this->id(id); + return *this; + } + + /* Select second identifier, initialize it with id associated with type */ + template + Base& second() { + this->second(_::cpp_type::id(this->world_v())); + return *this; + } + + /* Select second identifier, initialize it with name. If name starts with a $ + * the name is interpreted as a variable. */ + Base& second(const char *name) { + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + this->second(); + if (name[0] == '$') { + this->var(&name[1]); + } else { + this->name(name); + } + return *this; + } + + /** Set role of term. */ + Base& role(id_t role) { + this->assert_term(); + m_term->id_flags = role; + return *this; + } + + /** Set read/write access of term. */ + Base& inout(flecs::inout_kind_t inout) { + this->assert_term(); + m_term->inout = static_cast(inout); + return *this; + } + + /** Set read/write access for stage. Use this when a system reads or writes + * components other than the ones provided by the query. This information + * can be used by schedulers to insert sync/merge points between systems + * where deferred operations are flushed. + * + * Setting this is optional. If not set, the value of the accessed component + * may be out of sync for at most one frame. + */ + Base& inout_stage(flecs::inout_kind_t inout) { + this->assert_term(); + m_term->inout = static_cast(inout); + if (m_term->oper != EcsNot) { + this->src().entity(0); + } + return *this; + } + + /** Short for inout_stage(flecs::Out). + * Use when system uses add, remove or set. + */ + Base& write() { + return this->inout_stage(flecs::Out); + } + + /** Short for inout_stage(flecs::In). + * Use when system uses get. + */ + Base& read() { + return this->inout_stage(flecs::In); + } + + /** Short for inout_stage(flecs::InOut). + * Use when system uses get_mut. + */ + Base& read_write() { + return this->inout_stage(flecs::InOut); + } + + /** Short for inout(flecs::In) */ + Base& in() { + return this->inout(flecs::In); + } + + /** Short for inout(flecs::Out) */ + Base& out() { + return this->inout(flecs::Out); + } + + /** Short for inout(flecs::InOut) */ + Base& inout() { + return this->inout(flecs::InOut); + } + + /** Short for inout(flecs::In) */ + Base& inout_none() { + return this->inout(flecs::InOutNone); + } + + /** Set operator of term. */ + Base& oper(flecs::oper_kind_t oper) { + this->assert_term(); + m_term->oper = static_cast(oper); + return *this; + } + + /* Short for oper(flecs::And) */ + Base& and_() { + return this->oper(flecs::And); + } + + /* Short for oper(flecs::Or) */ + Base& or_() { + return this->oper(flecs::Or); + } + + /* Short for oper(flecs::Or) */ + Base& not_() { + return this->oper(flecs::Not); + } + + /* Short for oper(flecs::Or) */ + Base& optional() { + return this->oper(flecs::Optional); + } + + /* Short for oper(flecs::AndFrom) */ + Base& and_from() { + return this->oper(flecs::AndFrom); + } + + /* Short for oper(flecs::OrFrom) */ + Base& or_from() { + return this->oper(flecs::OrFrom); + } + + /* Short for oper(flecs::NotFrom) */ + Base& not_from() { + return this->oper(flecs::NotFrom); + } + + /** Match singleton. */ + Base& singleton() { + this->assert_term(); + ecs_assert(m_term->id || m_term->first.id, ECS_INVALID_PARAMETER, + "no component specified for singleton"); + + flecs::id_t sid = m_term->id; + if (!sid) { + sid = m_term->first.id; + } + + ecs_assert(sid != 0, ECS_INVALID_PARAMETER, NULL); + + if (!ECS_IS_PAIR(sid)) { + m_term->src.id = sid; + } else { + m_term->src.id = ecs_pair_first(world(), sid); + } + return *this; + } + + /* Filter terms are not triggered on by observers */ + Base& filter() { + m_term->src.flags |= flecs::Filter; + return *this; + } + + ecs_term_t *m_term; + +protected: + virtual flecs::world_t* world_v() = 0; + + void set_term(ecs_term_t *term) { + m_term = term; + if (term) { + this->m_term_id = &m_term->src; // default to subject + } else { + this->m_term_id = nullptr; + } + } + +private: + void assert_term() { + ecs_assert(m_term != NULL, ECS_INVALID_PARAMETER, + "no active term (call .term() first)"); + } + + operator Base&() { + return *static_cast(this); + } +}; + +} + + +namespace flecs { + +/** Class that describes a term. + * + * @ingroup cpp_core_filters + */ +struct term final : term_builder_i { + term() + : term_builder_i(&value) + , value({}) + , m_world(nullptr) { value.move = true; } + + term(flecs::world_t *world_ptr) + : term_builder_i(&value) + , value({}) + , m_world(world_ptr) { value.move = true; } + + term(flecs::world_t *world_ptr, ecs_term_t t) + : term_builder_i(&value) + , value({}) + , m_world(world_ptr) { + value = t; + value.move = false; + this->set_term(&value); + } + + term(flecs::world_t *world_ptr, id_t id) + : term_builder_i(&value) + , value({}) + , m_world(world_ptr) { + if (id & ECS_ID_FLAGS_MASK) { + value.id = id; + } else { + value.first.id = id; + } + value.move = false; + this->set_term(&value); + } + + term(flecs::world_t *world_ptr, entity_t r, entity_t o) + : term_builder_i(&value) + , value({}) + , m_world(world_ptr) { + value.id = ecs_pair(r, o); + value.move = false; + this->set_term(&value); + } + + term(id_t id) + : term_builder_i(&value) + , value({}) + , m_world(nullptr) { + if (id & ECS_ID_FLAGS_MASK) { + value.id = id; + } else { + value.first.id = id; + } + value.move = true; + } + + term(id_t r, id_t o) + : term_builder_i(&value) + , value({}) + , m_world(nullptr) { + value.id = ecs_pair(r, o); + value.move = true; + } + + term(const term& t) : term_builder_i(&value) { + m_world = t.m_world; + value = ecs_term_copy(&t.value); + this->set_term(&value); + } + + term(term&& t) noexcept : term_builder_i(&value) { + m_world = t.m_world; + value = ecs_term_move(&t.value); + t.reset(); + this->set_term(&value); + } + + term& operator=(const term& t) { + ecs_assert(m_world == t.m_world, ECS_INVALID_PARAMETER, NULL); + ecs_term_fini(&value); + value = ecs_term_copy(&t.value); + this->set_term(&value); + return *this; + } + + term& operator=(term&& t) noexcept { + ecs_assert(m_world == t.m_world, ECS_INVALID_PARAMETER, NULL); + ecs_term_fini(&value); + value = t.value; + this->set_term(&value); + t.reset(); + return *this; + } + + ~term() { + ecs_term_fini(&value); + } + + void reset() { + value = {}; + this->set_term(nullptr); + } + + int finalize() { + return ecs_term_finalize(m_world, &value); + } + + bool is_set() { + return ecs_term_is_initialized(&value); + } + + flecs::id id() { + return flecs::id(m_world, value.id); + } + + flecs::inout_kind_t inout() { + return static_cast(value.inout); + } + + flecs::oper_kind_t oper() { + return static_cast(value.oper); + } + + flecs::entity get_src() { + return flecs::entity(m_world, value.src.id); + } + + flecs::entity get_first() { + return flecs::entity(m_world, value.first.id); + } + + flecs::entity get_second() { + return flecs::entity(m_world, value.second.id); + } + + ecs_term_t move() { /* explicit move to ecs_term_t */ + return ecs_term_move(&value); + } + + ecs_term_t value; + +protected: + flecs::world_t* world_v() override { return m_world; } + +private: + flecs::world_t *m_world; +}; + +// Term mixin implementation +template +inline flecs::term world::term(Args &&... args) const { + return flecs::term(m_world, FLECS_FWD(args)...); +} + +template +inline flecs::term world::term() const { + return flecs::term(m_world, _::cpp_type::id(m_world)); +} + +template +inline flecs::term world::term() const { + return flecs::term(m_world, ecs_pair( + _::cpp_type::id(m_world), + _::cpp_type::id(m_world))); +} + +} + +/** + * @file addons/cpp/mixins/filter/impl.hpp + * @brief Filter implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/filter/builder.hpp + * @brief Filter builder. + */ + +#pragma once + +/** + * @file addons/cpp/utils/builder.hpp + * @brief Builder base class. + * + * Generic functionality for builder classes. + */ + +#pragma once + +namespace flecs { +namespace _ { + +// Macros for template types so we don't go cross-eyed +#define FLECS_TBUILDER template class +#define FLECS_IBUILDER template class + +template +struct builder : IBuilder +{ + using IBase = IBuilder; + +public: + builder(flecs::world_t *world) + : IBase(&m_desc) + , m_desc{} + , m_world(world) { } + + builder(const builder& f) + : IBase(&m_desc, f.m_term_index) + { + m_world = f.m_world; + m_desc = f.m_desc; + } + + builder(builder&& f) noexcept + : builder(f) { } + + operator TDesc*() { + return &m_desc; + } + + T build() { + return T(m_world, *static_cast(this)); + } + +protected: + flecs::world_t* world_v() override { return m_world; } + TDesc m_desc; + flecs::world_t *m_world; +}; + +#undef FLECS_TBUILDER +#undef FLECS_IBUILDER + +} // namespace _ +} // namespace flecs + +/** + * @file addons/cpp/mixins/filter/builder_i.hpp + * @brief Filter builder interface. + */ + +#pragma once + + +namespace flecs +{ + +/** Filter builder interface. + * + * @ingroup cpp_core_filters + */ +template +struct filter_builder_i : term_builder_i { + filter_builder_i(ecs_filter_desc_t *desc, int32_t term_index = 0) + : m_term_index(term_index) + , m_expr_count(0) + , m_desc(desc) { } + + Base& instanced() { + m_desc->instanced = true; + return *this; + } + + Base& filter_flags(ecs_flags32_t flags) { + m_desc->flags |= flags; + return *this; + } + + Base& expr(const char *expr) { + ecs_check(m_expr_count == 0, ECS_INVALID_OPERATION, + "filter_builder::expr() called more than once"); + m_desc->expr = expr; + m_expr_count ++; + + error: + return *this; + } + + /* With/without shorthand notation. */ + + template + Base& with(Args&&... args) { + return this->term(FLECS_FWD(args)...).inout_none(); + } + + template + Base& with(Args&&... args) { + return this->term(FLECS_FWD(args)...).inout_none(); + } + + template + Base& with() { + return this->term().inout_none(); + } + + template + Base& without(Args&&... args) { + return this->term(FLECS_FWD(args)...).not_(); + } + + template + Base& without(Args&&... args) { + return this->term(FLECS_FWD(args)...).not_(); + } + + template + Base& without() { + return this->term().not_(); + } + + /* Write/read shorthand notation */ + + Base& write() { + term_builder_i::write(); + return *this; + } + + template + Base& write(Args&&... args) { + return this->term(FLECS_FWD(args)...).write(); + } + + template + Base& write(Args&&... args) { + return this->term(FLECS_FWD(args)...).write(); + } + + template + Base& write() { + return this->term().write(); + } + + Base& read() { + term_builder_i::read(); + return *this; + } + + template + Base& read(Args&&... args) { + return this->term(FLECS_FWD(args)...).read(); + } + + template + Base& read(Args&&... args) { + return this->term(FLECS_FWD(args)...).read(); + } + + template + Base& read() { + return this->term().read(); + } + + /* Scope_open/scope_close shorthand notation. */ + Base& scope_open() { + return this->with(flecs::ScopeOpen).entity(0); + } + + Base& scope_close() { + return this->with(flecs::ScopeClose).entity(0); + } + + /* Term notation for more complex query features */ + + Base& term() { + if (this->m_term) { + ecs_check(ecs_term_is_initialized(this->m_term), + ECS_INVALID_OPERATION, + "filter_builder::term() called without initializing term"); + } + + if (m_term_index >= FLECS_TERM_DESC_MAX) { + if (m_term_index == FLECS_TERM_DESC_MAX) { + m_desc->terms_buffer = ecs_os_calloc_n( + ecs_term_t, m_term_index + 1); + ecs_os_memcpy_n(m_desc->terms_buffer, m_desc->terms, + ecs_term_t, m_term_index); + ecs_os_memset_n(m_desc->terms, 0, + ecs_term_t, FLECS_TERM_DESC_MAX); + } else { + m_desc->terms_buffer = ecs_os_realloc_n(m_desc->terms_buffer, + ecs_term_t, m_term_index + 1); + } + + m_desc->terms_buffer_count = m_term_index + 1; + + this->set_term(&m_desc->terms_buffer[m_term_index]); + } else { + this->set_term(&m_desc->terms[m_term_index]); + } + + m_term_index ++; + + error: + return *this; + } + + Base& term_at(int32_t term_index) { + ecs_assert(term_index > 0, ECS_INVALID_PARAMETER, NULL); + int32_t prev_index = m_term_index; + m_term_index = term_index - 1; + this->term(); + m_term_index = prev_index; + ecs_assert(ecs_term_is_initialized(this->m_term), + ECS_INVALID_PARAMETER, NULL); + return *this; + } + + Base& arg(int32_t term_index) { + return this->term_at(term_index); + } + + template + Base& term() { + this->term(); + *this->m_term = flecs::term(_::cpp_type::id(this->world_v())).move(); + this->m_term->inout = static_cast( + _::type_to_inout()); + return *this; + } + + Base& term(id_t id) { + this->term(); + *this->m_term = flecs::term(id).move(); + return *this; + } + + Base& term(const char *name) { + this->term(); + *this->m_term = flecs::term().first(name).move(); + return *this; + } + + Base& term(const char *first, const char *second) { + this->term(); + *this->m_term = flecs::term().first(first).second(second).move(); + return *this; + } + + Base& term(entity_t r, entity_t o) { + this->term(); + *this->m_term = flecs::term(r, o).move(); + return *this; + } + + Base& term(entity_t r, const char *o) { + this->term(); + *this->m_term = flecs::term(r).second(o).move(); + return *this; + } + + template + Base& term(id_t o) { + return this->term(_::cpp_type::id(this->world_v()), o); + } + + template + Base& term(const char *second) { + return this->term(_::cpp_type::id(this->world_v())).second(second); + } + + template + Base& term() { + return this->term(_::cpp_type::id(this->world_v())); + } + + template ::value > = 0> + Base& term(E value) { + flecs::entity_t r = _::cpp_type::id(this->world_v()); + auto o = enum_type(this->world_v()).entity(value); + return this->term(r, o); + } + + Base& term(flecs::term& term) { + this->term(); + *this->m_term = term.move(); + return *this; + } + + Base& term(flecs::term&& term) { + this->term(); + *this->m_term = term.move(); + return *this; + } + +protected: + virtual flecs::world_t* world_v() = 0; + int32_t m_term_index; + int32_t m_expr_count; + +private: + operator Base&() { + return *static_cast(this); + } + + ecs_filter_desc_t *m_desc; +}; + +} + + +namespace flecs { +namespace _ { + template + using filter_builder_base = builder< + filter, ecs_filter_desc_t, filter_builder, + filter_builder_i, Components ...>; +} + +/** Filter builder. + * + * @ingroup cpp_core_filters + */ +template +struct filter_builder final : _::filter_builder_base { + filter_builder(flecs::world_t* world, const char *name = nullptr) + : _::filter_builder_base(world) + { + _::sig(world).populate(this); + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; + this->m_desc.entity = ecs_entity_init(world, &entity_desc); + } + } + + template + void each(Func&& func) { + this->build().each(FLECS_FWD(func)); + } +}; + +} + + +namespace flecs +{ + +struct filter_base { + filter_base() + : m_world(nullptr) + , m_filter({}) + , m_filter_ptr(nullptr) { } + + filter_base(world_t *world, const ecs_filter_t *filter) + : m_world(world) + , m_filter({}) + , m_filter_ptr(filter) { } + + filter_base(world_t *world, ecs_filter_t *filter) + : m_world(world) + , m_filter_ptr(&m_filter) { + ecs_filter_move(&m_filter, filter); + } + + filter_base(world_t *world, ecs_filter_desc_t *desc) + : m_world(world) + { + desc->storage = &m_filter; + + if (ecs_filter_init(world, desc) == NULL) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + if (desc->terms_buffer) { + ecs_os_free(desc->terms_buffer); + } + + m_filter_ptr = &m_filter; + } + + filter_base(const filter_base& obj) { + this->m_world = obj.m_world; + if (obj.m_filter_ptr) { + this->m_filter_ptr = &this->m_filter; + } else { + this->m_filter_ptr = nullptr; + } + ecs_filter_copy(&m_filter, &obj.m_filter); + } + + filter_base& operator=(const filter_base& obj) { + this->m_world = obj.m_world; + if (obj.m_filter_ptr) { + this->m_filter_ptr = &this->m_filter; + } else { + this->m_filter_ptr = nullptr; + } + ecs_filter_copy(&m_filter, &obj.m_filter); + return *this; + } + + filter_base(filter_base&& obj) noexcept { + this->m_world = obj.m_world; + if (obj.m_filter_ptr) { + this->m_filter_ptr = &this->m_filter; + } else { + this->m_filter_ptr = nullptr; + } + ecs_filter_move(&m_filter, &obj.m_filter); + } + + filter_base& operator=(filter_base&& obj) noexcept { + this->m_world = obj.m_world; + if (obj.m_filter_ptr) { + this->m_filter_ptr = &this->m_filter; + } else { + this->m_filter_ptr = nullptr; + } + ecs_filter_move(&m_filter, &obj.m_filter); + return *this; + } + + flecs::entity entity() { + return flecs::entity(m_world, ecs_get_entity(m_filter_ptr)); + } + + operator const flecs::filter_t*() const { + return m_filter_ptr; + } + + /** Free the filter. + */ + ~filter_base() { + if ((&m_filter == m_filter_ptr) && m_filter_ptr) { + ecs_filter_fini(&m_filter); + } + } + + template + void each_term(const Func& func) { + for (int i = 0; i < m_filter_ptr->term_count; i ++) { + flecs::term t(m_world, m_filter_ptr->terms[i]); + func(t); + t.reset(); // prevent freeing resources + } + } + + flecs::term term(int32_t index) { + return flecs::term(m_world, m_filter_ptr->terms[index]); + } + + int32_t field_count() { + return m_filter_ptr->term_count; + } + + flecs::string str() { + char *result = ecs_filter_str(m_world, m_filter_ptr); + return flecs::string(result); + } + + operator filter<>() const; + +protected: + world_t *m_world = nullptr; + filter_t m_filter = ECS_FILTER_INIT; + const filter_t *m_filter_ptr; +}; + +template +struct filter : filter_base, iterable { +private: + using Terms = typename _::term_ptrs::array; + +public: + using filter_base::filter_base; + + filter() : filter_base() { } // necessary not to confuse msvc + + filter(const filter& obj) : filter_base(obj) { } + + filter& operator=(const filter& obj) { + filter_base::operator=(obj); + return *this; + } + + filter(filter&& obj) noexcept : filter_base(FLECS_MOV(obj)) { } + + filter& operator=(filter&& obj) noexcept { + filter_base::operator=(FLECS_FWD(obj)); + return *this; + } + +private: + ecs_iter_t get_iter(flecs::world_t *world) const override { + if (!world) { + world = m_world; + } + return ecs_filter_iter(world, m_filter_ptr); + } + + ecs_iter_next_action_t next_action() const override { + return ecs_filter_next; + } + + ecs_iter_next_action_t next_each_action() const override { + return ecs_filter_next_instanced; + } +}; + +// World mixin implementation +template +inline flecs::filter world::filter(Args &&... args) const { + return flecs::filter_builder(m_world, FLECS_FWD(args)...) + .build(); +} + +template +inline flecs::filter_builder world::filter_builder(Args &&... args) const { + return flecs::filter_builder(m_world, FLECS_FWD(args)...); +} + +// world::each +namespace _ { + +// Each with entity parameter +template +struct filter_delegate_w_ent; + +template +struct filter_delegate_w_ent > +{ + filter_delegate_w_ent(const flecs::world& world, Func&& func) { + auto f = world.filter(); + f.each(FLECS_MOV(func)); + } +}; + +// Each without entity parameter +template +struct filter_delegate_no_ent; + +template +struct filter_delegate_no_ent > +{ + filter_delegate_no_ent(const flecs::world& world, Func&& func) { + auto f = world.filter(); + f.each(FLECS_MOV(func)); + } +}; + +// Switch between function with & without entity parameter +template +struct filter_delegate; + +template +struct filter_delegate, flecs::entity>::value> > { + filter_delegate(const flecs::world& world, Func&& func) { + filter_delegate_w_ent>(world, FLECS_MOV(func)); + } +}; + +template +struct filter_delegate, flecs::entity>::value> > { + filter_delegate(const flecs::world& world, Func&& func) { + filter_delegate_no_ent>(world, FLECS_MOV(func)); + } +}; + +} + +template +inline void world::each(Func&& func) const { + _::filter_delegate f_delegate(*this, FLECS_MOV(func)); +} + +template +inline void world::each(Func&& func) const { + ecs_term_t t = {}; + t.id = _::cpp_type::id(); + ecs_iter_t it = ecs_term_iter(m_world, &t); + + while (ecs_term_next(&it)) { + _::each_delegate(func).invoke(&it); + } +} + +template +inline void world::each(flecs::id_t term_id, Func&& func) const { + ecs_term_t t = {}; + t.id = term_id; + ecs_iter_t it = ecs_term_iter(m_world, &t); + + while (ecs_term_next(&it)) { + _::each_delegate(func).invoke(&it); + } +} + +// filter_base implementation +inline filter_base::operator flecs::filter<> () const { + flecs::filter<> f; + ecs_filter_copy(&f.m_filter, &this->m_filter); + f.m_filter_ptr = &f.m_filter; + f.m_world = this->m_world; + return f; +} + +} + +/** + * @file addons/cpp/mixins/query/impl.hpp + * @brief Query implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/query/builder.hpp + * @brief Query builder. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/query/builder_i.hpp + * @brief Query builder interface. + */ + +#pragma once + + +namespace flecs { + +/** Query builder interface. + * + * @ingroup cpp_core_queries + */ +template +struct query_builder_i : filter_builder_i { +private: + using BaseClass = filter_builder_i; + +public: + query_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) { } + + query_builder_i(ecs_query_desc_t *desc, int32_t term_index = 0) + : BaseClass(&desc->filter, term_index) + , m_desc(desc) { } + + /** 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 (down)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 + Base& order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + ecs_order_by_action_t cmp = reinterpret_cast(compare); + return this->order_by(_::cpp_type::id(this->world_v()), cmp); + } + + /** 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. + */ + Base& order_by(flecs::entity_t component, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { + m_desc->order_by = reinterpret_cast(compare); + m_desc->order_by_component = component; + return *this; + } + + /** Group and sort matched tables. + * Similar to 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 group_by_action Callback that determines group id for table. + */ + template + Base& group_by(uint64_t(*group_by_action)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { + ecs_group_by_action_t action = reinterpret_cast(group_by_action); + return this->group_by(_::cpp_type::id(this->world_v()), action); + } + + /** Group and sort matched tables. + * Same as group_by, but with component identifier. + * + * @param component The component used to determine the group rank. + * @param group_by_action Callback that determines group id for table. + */ + Base& group_by(flecs::entity_t component, uint64_t(*group_by_action)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { + m_desc->group_by = reinterpret_cast(group_by_action); + m_desc->group_by_id = component; + return *this; + } + + /** Group and sort matched tables. + * Same as group_by, but with default group_by action. + * + * @tparam T The component used to determine the group rank. + */ + template + Base& group_by() { + return this->group_by(_::cpp_type::id(this->world_v()), nullptr); + } + + /** Group and sort matched tables. + * Same as group_by, but with default group_by action. + * + * @param component The component used to determine the group rank. + */ + Base& group_by(flecs::entity_t component) { + return this->group_by(component, nullptr); + } + + /** Specify context to be passed to group_by function. + * + * @param ctx Context to pass to group_by function. + * @param ctx_free Function to cleanup context (called when query is deleted). + */ + Base& group_by_ctx(void *ctx, ecs_ctx_free_t ctx_free = nullptr) { + m_desc->group_by_ctx = ctx; + m_desc->group_by_ctx_free = ctx_free; + return *this; + } + + /** Specify on_group_create action. + */ + Base& on_group_create(ecs_group_create_action_t action) { + m_desc->on_group_create = action; + return *this; + } + + /** Specify on_group_delete action. + */ + Base& on_group_delete(ecs_group_delete_action_t action) { + m_desc->on_group_delete = action; + return *this; + } + + /** Specify parent query (creates subquery) */ + Base& observable(const query_base& parent); + +protected: + virtual flecs::world_t* world_v() = 0; + +private: + operator Base&() { + return *static_cast(this); + } + + ecs_query_desc_t *m_desc; +}; + +} + + +namespace flecs { +namespace _ { + template + using query_builder_base = builder< + query, ecs_query_desc_t, query_builder, + query_builder_i, Components ...>; +} + +/** Query builder. + * + * @ingroup cpp_core_queries + */ +template +struct query_builder final : _::query_builder_base { + query_builder(flecs::world_t* world, const char *name = nullptr) + : _::query_builder_base(world) + { + _::sig(world).populate(this); + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; + this->m_desc.filter.entity = ecs_entity_init(world, &entity_desc); + } + } +}; + +} + + +namespace flecs { + +//////////////////////////////////////////////////////////////////////////////// +//// Persistent queries +//////////////////////////////////////////////////////////////////////////////// + +struct query_base { + query_base() + : m_world(nullptr) + , m_query(nullptr) { } + + query_base(world_t *world, query_t *query = nullptr) + : m_world(world) + , m_query(query) { } + + query_base(world_t *world, ecs_query_desc_t *desc) + : m_world(world) + { + m_query = ecs_query_init(world, desc); + + if (!m_query) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + if (desc->filter.terms_buffer) { + ecs_os_free(desc->filter.terms_buffer); + } + } + + operator query_t*() const { + return m_query; + } + + /** 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() const { + return ecs_query_changed(m_query, 0); + } + + /** 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() const { + return ecs_query_orphaned(m_query); + } + + /** Get info for group. + * + * @param group_id The group id for which to retrieve the info. + * @return The group info. + */ + const flecs::query_group_info_t* group_info(uint64_t group_id) const { + return ecs_query_get_group_info(m_query, group_id); + } + + /** Get context for group. + * + * @param group_id The group id for which to retrieve the context. + * @return The group context. + */ + void* group_ctx(uint64_t group_id) const { + const flecs::query_group_info_t *gi = group_info(group_id); + if (gi) { + return gi->ctx; + } else { + return NULL; + } + } + + /** Free the query. + */ + void destruct() { + ecs_query_fini(m_query); + m_world = nullptr; + m_query = nullptr; + } + + template + void each_term(const Func& func) const { + this->filter().each_term(func); + } + + filter_base filter() const { + return filter_base(m_world, ecs_query_get_filter(m_query)); + } + + flecs::term term(int32_t index) const { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs::term(m_world, f->terms[index]); + } + + int32_t field_count() const { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + return f->term_count; + } + + flecs::string str() const { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + char *result = ecs_filter_str(m_world, f); + return flecs::string(result); + } + + flecs::entity entity() const { + return flecs::entity(m_world, ecs_get_entity(m_query)); + } + + operator query<>() const; + +protected: + world_t *m_world; + query_t *m_query; +}; + +template +struct query final : query_base, iterable { +public: + flecs::world world() const { + return flecs::world(m_world); + } + +private: + using Terms = typename _::term_ptrs::array; + + ecs_iter_t get_iter(flecs::world_t *world) const override { + if (!world) { + world = m_world; + } + return ecs_query_iter(world, m_query); + } + + ecs_iter_next_action_t next_action() const override { + return ecs_query_next; + } + + ecs_iter_next_action_t next_each_action() const override { + return ecs_query_next_instanced; + } + +public: + using query_base::query_base; +}; + +// Mixin implementation +template +inline flecs::query world::query(Args &&... args) const { + return flecs::query_builder(m_world, FLECS_FWD(args)...) + .build(); +} + +template +inline flecs::query_builder world::query_builder(Args &&... args) const { + return flecs::query_builder(m_world, FLECS_FWD(args)...); +} + +// Builder implementation +template +inline Base& query_builder_i::observable(const query_base& parent) { + m_desc->parent = parent; + return *static_cast(this); +} + +// query_base implementation +inline query_base::operator query<>() const { + return flecs::query<>(m_world, m_query); +} + +} // namespace flecs + +/** + * @file addons/cpp/mixins/observer/impl.hpp + * @brief Observer implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/observer/builder.hpp + * @brief Observer builder. + */ + +#pragma once + +/** + * @file addons/cpp/utils/node_builder.hpp + * @brief Base builder class for node objects, like systems, observers. + */ + +#pragma once + +namespace flecs { +namespace _ { + +// Macros for template types so we don't go cross-eyed +#define FLECS_IBUILDER template class + +template +struct node_builder : IBuilder +{ + using IBase = IBuilder; + +public: + explicit node_builder(flecs::world_t* world, const char *name = nullptr) + : IBase(&m_desc) + , m_desc{} + , m_world(world) + , m_instanced(false) + { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; + m_desc.entity = ecs_entity_init(m_world, &entity_desc); + } + + /* 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 + T iter(Func&& func) { + using Delegate = typename _::iter_delegate< + typename std::decay::type, Components...>; + return build(FLECS_FWD(func)); + } + + /* Each is similar to action, but accepts a function that operates on a + * single entity */ + template + T each(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, Components...>; + m_instanced = true; + return build(FLECS_FWD(func)); + } + +protected: + flecs::world_t* world_v() override { return m_world; } + TDesc m_desc; + flecs::world_t *m_world; + bool m_instanced; + +private: + template + T build(Func&& func) { + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); + m_desc.callback = Delegate::run; + m_desc.binding_ctx = ctx; + m_desc.binding_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj); + + return T(m_world, &m_desc, m_instanced); + } +}; + +#undef FLECS_IBUILDER + +} // namespace _ +} // namespace flecs + +/** + * @file addons/cpp/mixins/observer/builder_i.hpp + * @brief Observer builder interface. + */ + +#pragma once + + +namespace flecs { + +/** Observer builder interface. + * + * @ingroup cpp_observers + */ +template +struct observer_builder_i : filter_builder_i { + using BaseClass = filter_builder_i; + observer_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) + , m_event_count(0) { } + + observer_builder_i(ecs_observer_desc_t *desc) + : BaseClass(&desc->filter) + , m_desc(desc) + , m_event_count(0) { } + + /** Specify the event(s) for when the observer should run. + * @param evt The event. + */ + Base& event(entity_t evt) { + m_desc->events[m_event_count ++] = evt; + return *this; + } + + /** Specify the event(s) for when the observer should run. + * @tparam E The event. + */ + template + Base& event() { + m_desc->events[m_event_count ++] = _::cpp_type().id(world_v()); + return *this; + } + + /** Invoke observer for anything that matches its filter on creation */ + Base& yield_existing(bool value = true) { + m_desc->yield_existing = value; + return *this; + } + + /** Set observer context */ + Base& ctx(void *ptr) { + m_desc->ctx = ptr; + return *this; + } + + /** Set observer run callback */ + Base& run(ecs_iter_action_t action) { + m_desc->run = action; + return *this; + } + +protected: + virtual flecs::world_t* world_v() = 0; + +private: + operator Base&() { + return *static_cast(this); + } + + ecs_observer_desc_t *m_desc; + int32_t m_event_count; +}; + +} + + +namespace flecs { +namespace _ { + template + using observer_builder_base = node_builder< + observer, ecs_observer_desc_t, observer_builder, + observer_builder_i, Components ...>; +} + +/** Observer builder. + * + * @ingroup cpp_observers + */ +template +struct observer_builder final : _::observer_builder_base { + observer_builder(flecs::world_t* world, const char *name = nullptr) + : _::observer_builder_base(world, name) + { + _::sig(world).populate(this); + } +}; + +} + + +namespace flecs +{ + +struct observer final : entity +{ + using entity::entity; + + explicit observer() : entity() { } + + observer(flecs::world_t *world, ecs_observer_desc_t *desc, bool instanced) + { + if (!desc->filter.instanced) { + desc->filter.instanced = instanced; + } + + m_world = world; + m_id = ecs_observer_init(world, desc); + + if (desc->filter.terms_buffer) { + ecs_os_free(desc->filter.terms_buffer); + } + } + + void ctx(void *ctx) { + ecs_observer_desc_t desc = {}; + desc.entity = m_id; + desc.ctx = ctx; + ecs_observer_init(m_world, &desc); + } + + void* ctx() const { + return ecs_observer_get_ctx(m_world, m_id); + } + + flecs::filter<> query() const { + const flecs::Poly *poly = this->get(flecs::Observer); + const ecs_observer_t *ob = static_cast(poly->poly); + return flecs::filter<>(m_world, &ob->filter); + } +}; + +// Mixin implementation +inline observer world::observer(flecs::entity e) const { + return flecs::observer(m_world, e); +} + +template +inline observer_builder world::observer(Args &&... args) const { + return flecs::observer_builder(m_world, FLECS_FWD(args)...); +} + +} // namespace flecs + +/** + * @file addons/cpp/mixins/event/impl.hpp + * @brief Event implementation. + */ + +#pragma once + + +namespace flecs +{ + +// Mixin implementation + +inline flecs::event_builder world::event(flecs::entity_t evt) const { + return flecs::event_builder(m_world, evt); +} + +template +inline flecs::event_builder_typed world::event() const { + return flecs::event_builder_typed(m_world, _::cpp_type().id(m_world)); +} + +namespace _ { + inline void entity_observer_create( + flecs::world_t *world, + flecs::entity_t event, + flecs::entity_t entity, + ecs_iter_action_t callback, + void *binding_ctx, + ecs_ctx_free_t binding_ctx_free) + { + ecs_observer_desc_t desc = {}; + desc.events[0] = event; + desc.filter.terms[0].id = EcsAny; + desc.filter.terms[0].src.id = entity; + desc.callback = callback; + desc.binding_ctx = binding_ctx; + desc.binding_ctx_free = binding_ctx_free; + + flecs::entity_t o = ecs_observer_init(world, &desc); + ecs_add_pair(world, o, EcsChildOf, entity); + } + + template + struct entity_observer_factory { + template ::value> = 0> + static void create( + flecs::world_t *world, + flecs::entity_t entity, + Func&& f) + { + using Delegate = _::entity_observer_delegate; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); + entity_observer_create(world, _::cpp_type::id(world), entity, Delegate::run, ctx, + reinterpret_cast(_::free_obj)); + } + + template ::value> = 0> + static void create( + flecs::world_t *world, + flecs::entity_t entity, + Func&& f) + { + using Delegate = _::entity_payload_observer_delegate; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); + entity_observer_create(world, _::cpp_type::id(world), entity, Delegate::run, ctx, + reinterpret_cast(_::free_obj)); + } + }; +} + +template +template +inline Self& entity_builder::observe(flecs::entity_t evt, Func&& f) { + using Delegate = _::entity_observer_delegate; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); + + _::entity_observer_create(m_world, evt, m_id, Delegate::run, ctx, + reinterpret_cast(_::free_obj)); + + return to_base(); +} + +template +template +inline Self& entity_builder::observe(Func&& f) { + _::entity_observer_factory::template create( + m_world, m_id, FLECS_FWD(f)); + return to_base(); +} + +template +template +inline Self& entity_builder::observe(Func&& f) { + return this->observe<_::event_from_func_t>(FLECS_FWD(f)); +} + +inline void entity_view::emit(flecs::entity evt) { + this->emit(evt.id()); +} + +inline void entity_view::enqueue(flecs::entity evt) { + this->enqueue(evt.id()); +} + +} // namespace flecs + +/** + * @file addons/cpp/mixins/enum/impl.hpp + * @brief Enum implementation. + */ + +#pragma once + +namespace flecs { + +template +inline E entity_view::to_constant() const { + const E* ptr = this->get(); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, "entity is not a constant"); + return ptr[0]; +} + +template ::value >> +inline flecs::entity world::to_entity(E constant) const { + const auto& et = enum_type(m_world); + return flecs::entity(m_world, et.entity(constant)); +} + +} +#ifdef FLECS_MODULE +/** + * @file addons/cpp/mixins/module/impl.hpp + * @brief Module implementation. + */ + +#pragma once + +namespace flecs { + +namespace _ { + +template +ecs_entity_t do_import(world& world, const char *symbol) { + ecs_trace("#[magenta]import#[reset] %s", _::type_name()); + ecs_log_push(); + + ecs_entity_t scope = ecs_set_scope(world, 0); + + // Initialize module component type & don't allow it to be registered as a + // tag, as this would prevent calling emplace() + auto m_c = component(world, nullptr, false); + ecs_add_id(world, m_c, EcsModule); + + ecs_set_scope(world, m_c); + world.emplace(world); + ecs_set_scope(world, scope); + + // It should now be possible to lookup the module + ecs_entity_t m = ecs_lookup_symbol(world, symbol, true, false); + ecs_assert(m != 0, ECS_MODULE_UNDEFINED, symbol); + ecs_assert(m == m_c, ECS_INTERNAL_ERROR, NULL); + + ecs_log_pop(); + + return m; +} + +template +flecs::entity import(world& world) { + const char *symbol = _::symbol_name(); + + ecs_entity_t m = ecs_lookup_symbol(world, symbol, true, false); + + if (!_::cpp_type::registered(world)) { + + /* Module is registered with world, initialize static data */ + if (m) { + _::cpp_type::init(m, false); + + /* Module is not yet registered, register it now */ + } else { + m = _::do_import(world, symbol); + } + + /* Module has been registered, but could have been for another world. Import + * if module hasn't been registered for this world. */ + } else if (!m) { + m = _::do_import(world, symbol); + } + + return flecs::entity(world, m); +} + +} + +/** + * @defgroup cpp_addons_modules Modules + * @ingroup cpp_addons + * Modules organize components, systems and more in reusable units of code. + * + * @{ + */ + +template +inline flecs::entity world::module(const char *name) const { + flecs::id_t result = _::cpp_type::id(m_world, nullptr, false); + if (name) { + ecs_add_path_w_sep(m_world, result, 0, name, "::", "::"); + } + ecs_set_scope(m_world, result); + return flecs::entity(m_world, result); +} + +template +inline flecs::entity world::import() { + return flecs::_::import(*this); +} + +/** @} */ + +} + +#endif +#ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/impl.hpp + * @brief System module implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/system/builder.hpp + * @brief System builder. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/system/builder_i.hpp + * @brief System builder interface. + */ + +#pragma once + + +namespace flecs +{ + +/** System builder interface. + * + * @ingroup cpp_addons_systems + */ +template +struct system_builder_i : query_builder_i { +private: + using BaseClass = query_builder_i; + +public: + system_builder_i(ecs_system_desc_t *desc) + : BaseClass(&desc->query) + , m_desc(desc) { } + + /** Specify in which phase the system should run. + * + * @param phase The phase. + */ + Base& kind(entity_t phase) { + flecs::entity_t cur_phase = ecs_get_target( + world_v(), m_desc->entity, EcsDependsOn, 0); + if (cur_phase) { + ecs_remove_id(world_v(), m_desc->entity, ecs_dependson(cur_phase)); + ecs_remove_id(world_v(), m_desc->entity, cur_phase); + } + if (phase) { + ecs_add_id(world_v(), m_desc->entity, ecs_dependson(phase)); + ecs_add_id(world_v(), m_desc->entity, phase); + } + return *this; + } + + /** Specify in which phase the system should run. + * + * @tparam Phase The phase. + */ + template + Base& kind() { + return this->kind(_::cpp_type::id(world_v())); + } + + /** Specify whether system can run on multiple threads. + * + * @param value If false system will always run on a single thread. + */ + Base& multi_threaded(bool value = true) { + m_desc->multi_threaded = value; + return *this; + } + + /** Specify whether system should be ran in staged context. + * + * @param value If false system will always run staged. + */ + Base& no_readonly(bool value = true) { + m_desc->no_readonly = value; + return *this; + } + + /** Set system interval. + * This operation will cause the system to be ran at the specified interval. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * @param interval The interval value. + */ + Base& interval(ecs_ftime_t interval) { + m_desc->interval = interval; + return *this; + } + + /** Set system rate. + * This operation will cause the system to be ran at a multiple of the + * provided tick source. The tick source may be any entity, including + * another system. + * + * @param tick_source The tick source. + * @param rate The multiple at which to run the system. + */ + Base& rate(const entity_t tick_source, int32_t rate) { + m_desc->rate = rate; + m_desc->tick_source = tick_source; + return *this; + } + + /** Set system rate. + * This operation will cause the system to be ran at a multiple of the + * frame tick frequency. If a tick source was provided, this just updates + * the rate of the system. + * + * @param rate The multiple at which to run the system. + */ + Base& rate(int32_t rate) { + m_desc->rate = rate; + return *this; + } + + /** Set tick source. + * This operation sets a shared tick source for the system. + * + * @tparam T The type associated with the singleton tick source to use for the system. + */ + template + Base& tick_source() { + m_desc->tick_source = _::cpp_type::id(world_v()); + return *this; + } + + /** Set tick source. + * This operation sets a shared tick source for the system. + * + * @param tick_source The tick source to use for the system. + */ + Base& tick_source(flecs::entity_t tick_source) { + m_desc->tick_source = tick_source; + return *this; + } + + /** Set system context */ + Base& ctx(void *ptr) { + m_desc->ctx = ptr; + return *this; + } + + /** Set system run callback */ + Base& run(ecs_iter_action_t action) { + m_desc->run = action; + return *this; + } + +protected: + virtual flecs::world_t* world_v() = 0; + +private: + operator Base&() { + return *static_cast(this); + } + + ecs_system_desc_t *m_desc; +}; + +} + + +namespace flecs { +namespace _ { + template + using system_builder_base = node_builder< + system, ecs_system_desc_t, system_builder, + system_builder_i, Components ...>; +} + +/** System builder. + * + * @ingroup cpp_addons_systems + */ +template +struct system_builder final : _::system_builder_base { + system_builder(flecs::world_t* world, const char *name = nullptr) + : _::system_builder_base(world, name) + { + _::sig(world).populate(this); + +#ifdef FLECS_PIPELINE + ecs_add_id(world, this->m_desc.entity, ecs_dependson(flecs::OnUpdate)); + ecs_add_id(world, this->m_desc.entity, flecs::OnUpdate); +#endif + } +}; + +} + + +namespace flecs +{ + +struct system_runner_fluent { + system_runner_fluent( + world_t *world, + entity_t id, + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) + : m_stage(world) + , m_id(id) + , m_delta_time(delta_time) + , m_param(param) + , m_offset(0) + , m_limit(0) + , m_stage_current(stage_current) + , m_stage_count(stage_count) { } + + system_runner_fluent& offset(int32_t offset) { + m_offset = offset; + return *this; + } + + system_runner_fluent& limit(int32_t limit) { + m_limit = limit; + return *this; + } + + system_runner_fluent& stage(flecs::world& stage) { + m_stage = stage.c_ptr(); + return *this; + } + + ~system_runner_fluent() { + if (m_stage_count) { + ecs_run_worker( + m_stage, m_id, m_stage_current, m_stage_count, m_delta_time, + m_param); + } else { + ecs_run_w_filter( + m_stage, m_id, m_delta_time, m_offset, m_limit, m_param); + } + } + +private: + world_t *m_stage; + entity_t m_id; + ecs_ftime_t m_delta_time; + void *m_param; + int32_t m_offset; + int32_t m_limit; + int32_t m_stage_current; + int32_t m_stage_count; +}; + +struct system final : entity +{ + using entity::entity; + + explicit system() { + m_id = 0; + m_world = nullptr; + } + + explicit system(flecs::world_t *world, ecs_system_desc_t *desc, bool instanced) + { + if (!desc->query.filter.instanced) { + desc->query.filter.instanced = instanced; + } + + m_world = world; + m_id = ecs_system_init(world, desc); + + if (desc->query.filter.terms_buffer) { + ecs_os_free(desc->query.filter.terms_buffer); + } + } + + void ctx(void *ctx) { + ecs_system_desc_t desc = {}; + desc.entity = m_id; + desc.ctx = ctx; + ecs_system_init(m_world, &desc); + } + + void* ctx() const { + return ecs_system_get_ctx(m_world, m_id); + } + + flecs::query<> query() const { + return flecs::query<>(m_world, ecs_system_get_query(m_world, m_id)); + } + + system_runner_fluent run(ecs_ftime_t delta_time = 0.0f, void *param = nullptr) const { + return system_runner_fluent(m_world, m_id, 0, 0, delta_time, param); + } + + system_runner_fluent run_worker( + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t delta_time = 0.0f, + void *param = nullptr) const + { + return system_runner_fluent( + m_world, m_id, stage_current, stage_count, delta_time, param); + } + +# ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/system_mixin.inl + * @brief Timer module system mixin. + */ + +/** + * @memberof flecs::system + * @ingroup cpp_addons_timer + * + * @{ + */ + +/** Set interval. + * @see ecs_set_interval + */ +void interval(ecs_ftime_t interval); + +/** Get interval. + * @see ecs_get_interval. + */ +ecs_ftime_t interval(); + +/** Set timeout. + * @see ecs_set_timeout + */ +void timeout(ecs_ftime_t timeout); + +/** Get timeout. + * @see ecs_get_timeout + */ +ecs_ftime_t timeout(); + +/** Set system rate (system is its own tick source). + * @see ecs_set_rate + */ +void rate(int32_t rate); + +/** Start timer. + * @see ecs_start_timer + */ +void start(); + +/** Stop timer. + * @see ecs_start_timer + */ +void stop(); + +/** Set external tick source. + * @see ecs_set_tick_source + */ +template +void set_tick_source(); + +/** Set external tick source. + * @see ecs_set_tick_source + */ +void set_tick_source(flecs::entity e); + +/** @} */ + +# endif + +}; + +// Mixin implementation +inline system world::system(flecs::entity e) const { + return flecs::system(m_world, e); +} + +template +inline system_builder world::system(Args &&... args) const { + return flecs::system_builder(m_world, FLECS_FWD(args)...); +} + +namespace _ { + +inline void system_init(flecs::world& world) { + world.component("flecs::system::TickSource"); +} + +} // namespace _ +} // namespace flecs + +#endif +#ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/impl.hpp + * @brief Pipeline module implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/pipeline/builder.hpp + * @brief Pipeline builder. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/pipeline/builder_i.hpp + * @brief Pipeline builder interface. + */ + +#pragma once + + +namespace flecs { + +/** Pipeline builder interface. + * + * @ingroup cpp_pipelines + */ +template +struct pipeline_builder_i : query_builder_i { + pipeline_builder_i(ecs_pipeline_desc_t *desc, int32_t term_index = 0) + : query_builder_i(&desc->query, term_index) + , m_desc(desc) { } + +private: + ecs_pipeline_desc_t *m_desc; +}; + +} + + +namespace flecs { +namespace _ { + template + using pipeline_builder_base = builder< + pipeline, ecs_pipeline_desc_t, pipeline_builder, + pipeline_builder_i, Components ...>; +} + +/** Pipeline builder. + * + * @ingroup cpp_pipelines + */ +template +struct pipeline_builder final : _::pipeline_builder_base { + pipeline_builder(flecs::world_t* world, flecs::entity_t id = 0) + : _::pipeline_builder_base(world) + { + _::sig(world).populate(this); + this->m_desc.entity = id; + } +}; + +} + + +namespace flecs { + +template +struct pipeline : entity { + pipeline(world_t *world, ecs_pipeline_desc_t *desc) + : entity(world) + { + m_id = ecs_pipeline_init(world, desc); + + if (!m_id) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + if (desc->query.filter.terms_buffer) { + ecs_os_free(desc->query.filter.terms_buffer); + } + } +}; + +inline flecs::pipeline_builder<> world::pipeline() const { + return flecs::pipeline_builder<>(m_world); +} + +template ::value >> +inline flecs::pipeline_builder<> world::pipeline() const { + return flecs::pipeline_builder<>(m_world, _::cpp_type::id(m_world)); +} + +inline void world::set_pipeline(const flecs::entity pip) const { + return ecs_set_pipeline(m_world, pip); +} + +template +inline void world::set_pipeline() const { + return ecs_set_pipeline(m_world, _::cpp_type::id(m_world)); +} + +inline flecs::entity world::get_pipeline() const { + return flecs::entity(m_world, ecs_get_pipeline(m_world)); +} + +inline bool world::progress(ecs_ftime_t delta_time) const { + return ecs_progress(m_world, delta_time); +} + +inline void world::run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time) const { + return ecs_run_pipeline(m_world, pip, delta_time); +} + +template ::value >> +inline void world::run_pipeline(ecs_ftime_t delta_time) const { + return ecs_run_pipeline(m_world, _::cpp_type::id(m_world), delta_time); +} + +inline void world::set_time_scale(ecs_ftime_t mul) const { + ecs_set_time_scale(m_world, mul); +} + +inline void world::set_target_fps(ecs_ftime_t target_fps) const { + ecs_set_target_fps(m_world, target_fps); +} + +inline void world::reset_clock() const { + ecs_reset_clock(m_world); +} + +inline void world::set_threads(int32_t threads) const { + ecs_set_threads(m_world, threads); +} + +inline int32_t world::get_threads() const { + return ecs_get_stage_count(m_world); +} + +inline void world::set_task_threads(int32_t task_threads) const { + ecs_set_task_threads(m_world, task_threads); +} + +inline bool world::using_task_threads() const { + return ecs_using_task_threads(m_world); +} + +} + +#endif +#ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/impl.hpp + * @brief Timer module implementation. + */ + +#pragma once + +namespace flecs { + +// Timer class +struct timer final : entity { + using entity::entity; + + timer& interval(ecs_ftime_t interval) { + ecs_set_interval(m_world, m_id, interval); + return *this; + } + + ecs_ftime_t interval() { + return ecs_get_interval(m_world, m_id); + } + + timer& timeout(ecs_ftime_t timeout) { + ecs_set_timeout(m_world, m_id, timeout); + return *this; + } + + ecs_ftime_t timeout() { + return ecs_get_timeout(m_world, m_id); + } + + timer& rate(int32_t rate, flecs::entity_t tick_source = 0) { + ecs_set_rate(m_world, m_id, rate, tick_source); + return *this; + } + + void start() { + ecs_start_timer(m_world, m_id); + } + + void stop() { + ecs_stop_timer(m_world, m_id); + } +}; + +template +inline flecs::timer world::timer() const { + return flecs::timer(m_world, _::cpp_type::id(m_world)); +} + +template +inline flecs::timer world::timer(Args &&... args) const { + return flecs::timer(m_world, FLECS_FWD(args)...); +} + +inline void world::randomize_timers() const { + ecs_randomize_timers(m_world); +} + +inline void system::interval(ecs_ftime_t interval) { + ecs_set_interval(m_world, m_id, interval); +} + +inline ecs_ftime_t system::interval() { + return ecs_get_interval(m_world, m_id); +} + +inline void system::timeout(ecs_ftime_t timeout) { + ecs_set_timeout(m_world, m_id, timeout); +} + +inline ecs_ftime_t system::timeout() { + return ecs_get_timeout(m_world, m_id); +} + +inline void system::rate(int32_t rate) { + ecs_set_rate(m_world, m_id, rate, 0); +} + +inline void system::start() { + ecs_start_timer(m_world, m_id); +} + +inline void system::stop() { + ecs_stop_timer(m_world, m_id); +} + +template +inline void system::set_tick_source() { + ecs_set_tick_source(m_world, m_id, _::cpp_type::id(m_world)); +} + +inline void system::set_tick_source(flecs::entity e) { + ecs_set_tick_source(m_world, m_id, e); +} + +namespace _ { + +inline void timer_init(flecs::world& world) { + world.component("flecs::timer::RateFilter"); + world.component("flecs::timer::Timer"); +} + +} +} + +#endif +#ifdef FLECS_SNAPSHOT +/** + * @file addons/cpp/mixins/snapshot/impl.hpp + * @brief Snapshot module implementation. + */ + +#pragma once + +namespace flecs { + +struct snapshot final { + 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); + m_snapshot = ecs_snapshot_take_w_iter(&it); + } + + snapshot(snapshot&& obj) noexcept + : 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); + m_snapshot = ecs_snapshot_take_w_iter(&it); + return *this; + } + + snapshot& operator=(snapshot&& obj) noexcept { + 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()); + } + + template + void take(const F& f) { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + + ecs_iter_t it = ecs_filter_iter(m_world, f.c_ptr()); + + m_snapshot = ecs_snapshot_take_w_iter(&it); + } + + 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; + } + +private: + const world& m_world; + snapshot_t *m_snapshot; +}; + +// Snapshot mixin implementation +template +inline flecs::snapshot world::snapshot(Args &&... args) const { + return flecs::snapshot(*this, FLECS_FWD(args)...); +} + +} + +#endif +#ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/impl.hpp + * @brief Doc mixin implementation. + */ + +#pragma once + +namespace flecs { +namespace doc { + +/** Get human readable name for an entity. + * + * @see ecs_doc_get_name() + * @see flecs::doc::set_name() + * @see flecs::entity_view::doc_name() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_name(const flecs::entity_view& e) { + return ecs_doc_get_name(e.world(), e); +} + +/** Get brief description for an entity. + * + * @see ecs_doc_get_brief() + * @see flecs::doc::set_brief() + * @see flecs::entity_view::doc_brief() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_brief(const flecs::entity_view& e) { + return ecs_doc_get_brief(e.world(), e); +} + +/** Get detailed description for an entity. + * + * @see ecs_doc_get_detail() + * @see flecs::doc::set_detail() + * @see flecs::entity_view::doc_detail() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_detail(const flecs::entity_view& e) { + return ecs_doc_get_detail(e.world(), e); +} + +/** Get link to external documentation for an entity. + * + * @see ecs_doc_get_link() + * @see flecs::doc::set_link() + * @see flecs::entity_view::doc_link() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_link(const flecs::entity_view& e) { + return ecs_doc_get_link(e.world(), e); +} + +/** Get color for an entity. + * + * @see ecs_doc_get_color() + * @see flecs::doc::set_color() + * @see flecs::entity_view::doc_color() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_color(const flecs::entity_view& e) { + return ecs_doc_get_color(e.world(), e); +} + +/** Set human readable name for an entity. + * + * @see ecs_doc_set_name() + * @see flecs::doc::get_name() + * @see flecs::entity_builder::set_doc_name() + * + * @ingroup cpp_addons_doc + */ +inline void set_name(flecs::entity& e, const char *name) { + ecs_doc_set_name(e.world(), e, name); +} + +/** Set brief description for an entity. + * + * @see ecs_doc_set_brief() + * @see flecs::doc::get_brief() + * @see flecs::entity_builder::set_doc_brief() + * + * @ingroup cpp_addons_doc + */ +inline void set_brief(flecs::entity& e, const char *description) { + ecs_doc_set_brief(e.world(), e, description); +} + +/** Set detailed description for an entity. + * + * @see ecs_doc_set_detail() + * @see flecs::doc::get_detail() + * @see flecs::entity_builder::set_doc_detail() + * + * @ingroup cpp_addons_doc + */ +inline void set_detail(flecs::entity& e, const char *description) { + ecs_doc_set_detail(e.world(), e, description); +} + +/** Set link to external documentation for an entity. + * + * @see ecs_doc_set_link() + * @see flecs::doc::get_link() + * @see flecs::entity_builder::set_doc_link() + * + * @ingroup cpp_addons_doc + */ +inline void set_link(flecs::entity& e, const char *link) { + ecs_doc_set_link(e.world(), e, link); +} + +/** Set color for an entity. + * + * @see ecs_doc_set_color() + * @see flecs::doc::get_color() + * @see flecs::entity_builder::set_doc_color() + * + * @ingroup cpp_addons_doc + */ +inline void set_color(flecs::entity& e, const char *color) { + ecs_doc_set_color(e.world(), e, color); +} + +/** @private */ +namespace _ { + +/** @private */ +inline void init(flecs::world& world) { + world.component("flecs::doc::Description"); +} + +} // namespace _ +} // namespace doc +} // namespace flecs + +#endif +#ifdef FLECS_DOC +#endif +#ifdef FLECS_REST +/** + * @file addons/cpp/mixins/rest/impl.hpp + * @brief Rest module implementation. + */ + +#pragma once + +namespace flecs { +namespace rest { +namespace _ { + +inline void init(flecs::world& world) { + world.component("flecs::rest::Rest"); +} + +} // namespace _ +} // namespace rest +} // namespace flecs + +#endif +#ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/impl.hpp + * @brief Rule implementation. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/rule/builder.hpp + * @brief Rule builder. + */ + +#pragma once + + +namespace flecs { +namespace _ { + template + using rule_builder_base = builder< + rule, ecs_filter_desc_t, rule_builder, + filter_builder_i, Components ...>; +} + +/** Rule builder. + * + * @ingroup cpp_addons_rules + */ +template +struct rule_builder final : _::rule_builder_base { + rule_builder(flecs::world_t* world, const char *name = nullptr) + : _::rule_builder_base(world) + { + _::sig(world).populate(this); + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; + this->m_desc.entity = ecs_entity_init(world, &entity_desc); + } + } +}; + +} + + +namespace flecs { + +//////////////////////////////////////////////////////////////////////////////// +//// Persistent queries +//////////////////////////////////////////////////////////////////////////////// + +struct rule_base { + rule_base() + : m_world(nullptr) + , m_rule(nullptr) { } + + rule_base(world_t *world, rule_t *rule = nullptr) + : m_world(world) + , m_rule(rule) { } + + rule_base(world_t *world, ecs_filter_desc_t *desc) + : m_world(world) + { + m_rule = ecs_rule_init(world, desc); + if (desc->terms_buffer) { + ecs_os_free(desc->terms_buffer); + } + } + + bool is_valid() const { + return m_rule != nullptr; + } + + operator rule_t*() const { + return m_rule; + } + + flecs::entity entity() { + return flecs::entity(m_world, ecs_get_entity(m_rule)); + } + + /** Free the rule. */ + void destruct() { + if (m_rule) { + ecs_rule_fini(m_rule); + m_world = nullptr; + m_rule = nullptr; + } + } + + template + void each_term(const Func& func) const { + this->filter().each_term(func); + } + + /** Move the rule. */ + void move(flecs::rule_base&& obj) { + this->destruct(); + this->m_world = obj.m_world; + this->m_rule = obj.m_rule; + obj.m_world = nullptr; + obj.m_rule = nullptr; + } + + flecs::filter_base filter() const { + return filter_base(m_world, ecs_rule_get_filter(m_rule)); + } + + /** Converts this rule to a string expression + * @see ecs_filter_str + */ + flecs::string str() const { + const ecs_filter_t *f = ecs_rule_get_filter(m_rule); + char *result = ecs_filter_str(m_world, f); + return flecs::string(result); + } + + + /** Converts this rule to a string that can be used to aid debugging + * the behavior of the rule. + * @see ecs_rule_str + */ + flecs::string rule_str() const { + char *result = ecs_rule_str(m_rule); + return flecs::string(result); + } + + operator rule<>() const; + +protected: + world_t *m_world; + rule_t *m_rule; +}; + +template +struct rule final : rule_base, iterable { +private: + using Terms = typename _::term_ptrs::array; + + ecs_iter_t get_iter(flecs::world_t *world) const override { + if (!world) { + world = m_world; + } + return ecs_rule_iter(world, m_rule); + } + + ecs_iter_next_action_t next_action() const override { + return ecs_rule_next; + } + + ecs_iter_next_action_t next_each_action() const override { + return ecs_rule_next_instanced; + } + +public: + using rule_base::rule_base; + + int32_t find_var(const char *name) { + return ecs_rule_find_var(m_rule, name); + } +}; + +// Mixin implementation +template +inline flecs::rule world::rule(Args &&... args) const { + return flecs::rule_builder(m_world, FLECS_FWD(args)...) + .build(); +} + +template +inline flecs::rule_builder world::rule_builder(Args &&... args) const { + return flecs::rule_builder(m_world, FLECS_FWD(args)...); +} + +// rule_base implementation +inline rule_base::operator rule<>() const { + return flecs::rule<>(m_world, m_rule); +} + +} // namespace flecs + +#endif +#ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/impl.hpp + * @brief Meta implementation. + */ + +#pragma once + +FLECS_ENUM_LAST(flecs::meta::type_kind_t, flecs::meta::TypeKindLast) +FLECS_ENUM_LAST(flecs::meta::primitive_kind_t, flecs::meta::PrimitiveKindLast) + +namespace flecs { +namespace meta { +namespace _ { + +/* Type support for entity wrappers */ +template +inline flecs::opaque flecs_entity_support(flecs::world&) { + return flecs::opaque() + .as_type(flecs::Entity) + .serialize([](const flecs::serializer *ser, const EntityType *data) { + flecs::entity_t id = data->id(); + return ser->value(flecs::Entity, &id); + }) + .assign_entity( + [](EntityType *dst, flecs::world_t *world, flecs::entity_t e) { + *dst = EntityType(world, e); + }); +} + +inline void init(flecs::world& world) { + world.component("flecs::meta::bool"); + world.component("flecs::meta::char"); + world.component("flecs::meta::u8"); + world.component("flecs::meta::u16"); + world.component("flecs::meta::u32"); + world.component("flecs::meta::u64"); + world.component("flecs::meta::i8"); + world.component("flecs::meta::i16"); + world.component("flecs::meta::i32"); + world.component("flecs::meta::i64"); + world.component("flecs::meta::f32"); + world.component("flecs::meta::f64"); + + world.component("flecs::meta::type_kind"); + world.component("flecs::meta::primitive_kind"); + world.component("flecs::meta::member"); + world.component("flecs::meta::enum_constant"); + world.component("flecs::meta::bitmask_constant"); + + world.component("flecs::meta::MetaType"); + world.component("flecs::meta::MetaTypeSerialized"); + world.component("flecs::meta::Primitive"); + world.component("flecs::meta::Enum"); + world.component("flecs::meta::Bitmask"); + world.component("flecs::meta::Member"); + world.component("flecs::meta::Struct"); + world.component("flecs::meta::Array"); + world.component("flecs::meta::Vector"); + + world.component("flecs::meta::Unit"); + + // To support member and member register components + // (that do not have conflicting symbols with builtin ones) for platform + // specific types. + + if (!flecs::is_same() && !flecs::is_same()) { + flecs::_::cpp_type::init(flecs::Iptr, true); + ecs_assert(flecs::type_id() == flecs::Iptr, + ECS_INTERNAL_ERROR, NULL); + // Remove symbol to prevent validation errors, as it doesn't match with + // the typename + ecs_remove_pair(world, flecs::Iptr, ecs_id(EcsIdentifier), EcsSymbol); + } + + if (!flecs::is_same() && !flecs::is_same()) { + flecs::_::cpp_type::init(flecs::Uptr, true); + ecs_assert(flecs::type_id() == flecs::Uptr, + ECS_INTERNAL_ERROR, NULL); + // Remove symbol to prevent validation errors, as it doesn't match with + // the typename + ecs_remove_pair(world, flecs::Uptr, ecs_id(EcsIdentifier), EcsSymbol); + } + + // Register opaque type support for C++ entity wrappers + world.component() + .opaque(flecs_entity_support); + + world.component() + .opaque(flecs_entity_support); +} + +} // namespace _ + +} // namespace meta + + +inline flecs::entity cursor::get_type() const { + return flecs::entity(m_cursor.world, ecs_meta_get_type(&m_cursor)); +} + +inline flecs::entity cursor::get_unit() const { + return flecs::entity(m_cursor.world, ecs_meta_get_unit(&m_cursor)); +} + +inline flecs::entity cursor::get_entity() const { + return flecs::entity(m_cursor.world, ecs_meta_get_entity(&m_cursor)); +} + +/** Create primitive type */ +inline flecs::entity world::primitive(flecs::meta::primitive_kind_t kind) { + ecs_primitive_desc_t desc = {}; + desc.kind = kind; + flecs::entity_t eid = ecs_primitive_init(m_world, &desc); + ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); + return flecs::entity(m_world, eid); +} + +/** Create array type. */ +inline flecs::entity world::array(flecs::entity_t elem_id, int32_t array_count) { + ecs_array_desc_t desc = {}; + desc.type = elem_id; + desc.count = array_count; + flecs::entity_t eid = ecs_array_init(m_world, &desc); + ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); + return flecs::entity(m_world, eid); +} + +/** Create array type. */ +template +inline flecs::entity world::array(int32_t array_count) { + return this->array(_::cpp_type::id(m_world), array_count); +} + +inline flecs::entity world::vector(flecs::entity_t elem_id) { + ecs_vector_desc_t desc = {}; + desc.type = elem_id; + flecs::entity_t eid = ecs_vector_init(m_world, &desc); + ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); + return flecs::entity(m_world, eid); +} + +template +inline flecs::entity world::vector() { + return this->vector(_::cpp_type::id(m_world)); +} + +} // namespace flecs + +inline int ecs_serializer_t::value(ecs_entity_t type, const void *v) const { + return this->value_(this, type, v); +} + +template +inline int ecs_serializer_t::value(const T& v) const { + return this->value(flecs::_::cpp_type::id( + const_cast(this->world)), &v); +} + +inline int ecs_serializer_t::member(const char *name) const { + return this->member_(this, name); +} + +#endif +#ifdef FLECS_UNITS +/** + * @file addons/cpp/mixins/units/impl.hpp + * @brief Units module implementation. + */ + +#pragma once + +namespace flecs { + +inline units::units(flecs::world& world) { + /* Import C module */ + FlecsUnitsImport(world); + + /* Bridge between C++ types and flecs.units entities */ + world.module(); + + // Initialize world.entity(prefixes) scope + world.entity("::flecs::units::prefixes"); + + // Initialize prefixes + world.entity("::flecs::units::prefixes::Yocto"); + world.entity("::flecs::units::prefixes::Zepto"); + world.entity("::flecs::units::prefixes::Atto"); + world.entity("::flecs::units::prefixes::Femto"); + world.entity("::flecs::units::prefixes::Pico"); + world.entity("::flecs::units::prefixes::Nano"); + world.entity("::flecs::units::prefixes::Micro"); + world.entity("::flecs::units::prefixes::Milli"); + world.entity("::flecs::units::prefixes::Centi"); + world.entity("::flecs::units::prefixes::Deci"); + world.entity("::flecs::units::prefixes::Deca"); + world.entity("::flecs::units::prefixes::Hecto"); + world.entity("::flecs::units::prefixes::Kilo"); + world.entity("::flecs::units::prefixes::Mega"); + world.entity("::flecs::units::prefixes::Giga"); + world.entity("::flecs::units::prefixes::Tera"); + world.entity("::flecs::units::prefixes::Peta"); + world.entity("::flecs::units::prefixes::Exa"); + world.entity("::flecs::units::prefixes::Zetta"); + world.entity("::flecs::units::prefixes::Yotta"); + world.entity("::flecs::units::prefixes::Kibi"); + world.entity("::flecs::units::prefixes::Mebi"); + world.entity("::flecs::units::prefixes::Gibi"); + world.entity("::flecs::units::prefixes::Tebi"); + world.entity("::flecs::units::prefixes::Pebi"); + world.entity("::flecs::units::prefixes::Exbi"); + world.entity("::flecs::units::prefixes::Zebi"); + world.entity("::flecs::units::prefixes::Yobi"); + + // Initialize quantities + world.entity("::flecs::units::Duration"); + world.entity