From 470b54c2b52d3e88bc8d38f25c8211ee36d33863 Mon Sep 17 00:00:00 2001 From: jazzay Date: Mon, 16 Oct 2023 12:27:11 -0700 Subject: [PATCH] Upgrade to Flecs 3.2.8 --- .justfile | 3 + flecs-sys/README.md | 2 +- flecs-sys/flecs.c | 99191 ++++++++++++++++++------------------ flecs-sys/flecs.h | 2652 +- flecs-sys/src/bindings.rs | 943 +- readme.md | 2 +- src/component.rs | 2 +- src/world.rs | 2 +- 8 files changed, 53244 insertions(+), 49553 deletions(-) diff --git a/.justfile b/.justfile index a72070c..448fa6f 100644 --- a/.justfile +++ b/.justfile @@ -22,3 +22,6 @@ bench: # Generates rust binding for Flecs C api bindings: cargo build --features export_bindings + +examples: + make run-examples \ No newline at end of file diff --git a/flecs-sys/README.md b/flecs-sys/README.md index 215d8d8..f5d6892 100644 --- a/flecs-sys/README.md +++ b/flecs-sys/README.md @@ -7,4 +7,4 @@ A Rust binding for the Flecs ECS library: -Wraps native Flecs v3.2.4 +Wraps native Flecs v3.2.8 diff --git a/flecs-sys/flecs.c b/flecs-sys/flecs.c index 31b3e9d..0d748e1 100644 --- a/flecs-sys/flecs.c +++ b/flecs-sys/flecs.c @@ -1,27 +1,19 @@ /** - * @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. + * @file bootstrap.c + * @brief Bootstrap entities in the flecs.core namespace. * - * 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. + * 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. * - * 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). + * 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. * - * Note that the actual data is not stored on the storage table. The storage - * table is only used for sharing administration. A storage_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. + * 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" @@ -230,15 +222,22 @@ typedef struct ecs_stack_page_t { typedef struct ecs_stack_t { ecs_stack_page_t first; - ecs_stack_page_t *cur; + 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, @@ -250,6 +249,7 @@ void* flecs_stack_alloc( #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, @@ -261,6 +261,7 @@ void* flecs_stack_calloc( #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); @@ -274,12 +275,14 @@ void flecs_stack_free( void flecs_stack_reset( ecs_stack_t *stack); -ecs_stack_cursor_t flecs_stack_get_cursor( +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, - const ecs_stack_cursor_t *cursor); + ecs_stack_cursor_t *cursor); #endif @@ -490,102 +493,25 @@ extern "C" { #endif +/** + * @file table.h + * @brief Table storage implementation. + */ -/* 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) - -/* 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; - -/* 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; +#ifndef FLECS_TABLE_H +#define FLECS_TABLE_H - /* If the nubmer of fields gets out of hand, this can be turned into a union - * but since events are very temporary objects, this works for now and makes - * initializing an event a bit simpler. */ -} ecs_table_event_t; +/** + * @file table_graph.h + * @brief Table graph types and functions. + */ -/** Stage-specific component data */ -struct ecs_data_t { - ecs_vec_t entities; /* Entity identifiers */ - ecs_vec_t records; /* Ptrs to records in main entity index */ - ecs_vec_t *columns; /* Component columns */ -}; +#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}} -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; - /** 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. */ @@ -594,6 +520,11 @@ typedef struct ecs_table_diff_builder_t { 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; @@ -625,14 +556,87 @@ typedef struct ecs_graph_node_t { 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 nubmer of fields gets out of hand, this can be turned into a union + * but since events are very temporary objects, this works for now and makes + * initializing an event a bit simpler. */ +} ecs_table_event_t; + /** 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 refcount; /* Increased when used as storage table */ - int32_t traversable_count; /* Number of observed entities in table */ + int32_t traversable_count; /* Traversable relationship targets in table */ uint16_t generation; /* Used for table cleanup */ - uint16_t record_count; /* Table record count including wildcards */ + 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 */ @@ -646,6 +650,21 @@ typedef struct ecs_table__t { 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_vec_t records; /* Ptrs to records in entity index */ + 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 @@ -653,24 +672,251 @@ typedef struct ecs_table__t { struct ecs_table_t { uint64_t id; /* Table id in sparse set */ ecs_flags32_t flags; /* Flags for testing table properties */ - uint16_t storage_count; /* Number of components (excluding tags) */ - ecs_type_t type; /* Identifies table type in type_index */ + int16_t column_count; /* Number of components (excluding tags) */ + ecs_type_t type; /* Vector with component ids */ - ecs_graph_node_t node; /* Graph node */ ecs_data_t data; /* Component storage */ - ecs_type_info_t **type_info; /* Cached type info */ + ecs_graph_node_t node; /* Graph node */ + int32_t *dirty_state; /* Keep track of changes in columns */ - - ecs_table_t *storage_table; /* Table without tags */ - ecs_id_t *storage_ids; /* Component ids (prevent indirection) */ - int32_t *storage_map; /* Map type <-> data type - * - 0..count(T): type -> data_type - * - count(T)..count(S): data_type -> type + 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, + ecs_record_t *record, + 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); + +#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) + +/* 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; @@ -847,6 +1093,13 @@ struct ecs_query_t { 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; @@ -977,7 +1230,7 @@ struct ecs_stage_t { 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 */ - ecs_entity_t *lookup_path; /* Search path used by lookup operations */ + const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Properties */ bool auto_merge; /* Should this stage automatically merge? */ @@ -1042,6 +1295,8 @@ typedef struct ecs_action_elem_t { 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 { @@ -1099,7 +1354,8 @@ struct ecs_world_t { 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 */ - bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ + 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 */ @@ -1119,7 +1375,12 @@ struct ecs_world_t { ecs_world_allocators_t allocators; /* Static allocation sizes */ ecs_allocator_t allocator; /* Dynamic allocation sizes */ - void *context; /* Application context */ + 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 */ }; @@ -1182,27 +1443,28 @@ 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_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))) + (ECS_CAST(T*, flecs_table_cache_next_(it))) #endif /** - * @file id_record.h + * @file id_index.h * @brief Index for looking up tables by (component) id. */ -#ifndef FLECS_ID_RECORD_H -#define FLECS_ID_RECORD_H +#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 */ - int32_t column; /* First column where id occurs in table */ - int32_t count; /* Number of times id occurs in table */ + 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 */ @@ -1323,7 +1585,7 @@ ecs_table_record_t* flecs_table_record_get( ecs_id_t id); /* Find table record for id record */ -const ecs_table_record_t* flecs_id_record_get_table( +ecs_table_record_t* flecs_id_record_get_table( const ecs_id_record_t *idr, const ecs_table_t *table); @@ -1458,246 +1720,6 @@ void flecs_iter_free( #endif -/** - * @file table.c - * @brief Table storage implementation. - */ - -#ifndef FLECS_TABLE_H -#define FLECS_TABLE_H - -/* 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, - ecs_record_t *record, - 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); - -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); - -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); - -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_clear_edges( - ecs_world_t *world, - ecs_table_t *table); - -void flecs_table_delete_entities( - ecs_world_t *world, - ecs_table_t *table); - -ecs_vec_t *ecs_table_column_for_id( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id); - -int32_t flecs_table_column_to_union_index( - const ecs_table_t *table, - int32_t column); - -/* Increase refcount of table (prevents deletion) */ -void flecs_table_claim( - ecs_world_t *world, - ecs_table_t *table); - -/* Decreases refcount of table (may delete) */ -bool flecs_table_release( - ecs_world_t *world, - ecs_table_t *table); - -/* Increase observer count of table */ -void flecs_table_traversable_add( - ecs_table_t *table, - int32_t value); - -/* 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 - /** * @file poly.h * @brief Functions for managing poly objects. @@ -1709,22 +1731,22 @@ void flecs_table_diff_build_noalloc( #include /* Initialize poly */ -void* _ecs_poly_init( +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) + ecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) /* Deinitialize object for specified type */ -void _ecs_poly_fini( +void ecs_poly_fini_( ecs_poly_t *object, int32_t kind); #define ecs_poly_fini(object, type)\ - _ecs_poly_fini(object, type##_magic) + ecs_poly_fini_(object, type##_magic) /* Utility functions for creating an object on the heap */ #define ecs_poly_new(type)\ @@ -1735,49 +1757,49 @@ void _ecs_poly_fini( ecs_os_free(obj) /* Get or create poly component for an entity */ -EcsPoly* _ecs_poly_bind( +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) + ecs_poly_bind_(world, entity, T##_tag) -void _ecs_poly_modified( +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) + ecs_poly_modified_(world, entity, T##_tag) /* Get poly component for an entity */ -const EcsPoly* _ecs_poly_bind_get( +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_bind_get_(world, entity, T##_tag) -ecs_poly_t* _ecs_poly_get( +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)) + ((T*)ecs_poly_get_(world, entity, T##_tag)) /* Utilities for testing/asserting an object type */ #ifndef FLECS_NDEBUG -void* _ecs_poly_assert( +void* ecs_poly_assert_( const ecs_poly_t *object, int32_t type, const char *file, int32_t line); #define ecs_poly_assert(object, type)\ - _ecs_poly_assert(object, type##_magic, __FILE__, __LINE__) + ecs_poly_assert_(object, type##_magic, __FILE__, __LINE__) #define ecs_poly(object, T) ((T*)ecs_poly_assert(object, T)) #else @@ -1901,7 +1923,7 @@ bool flecs_defer_purge( #endif /** - * @file world.c + * @file world.h * @brief World-level API. */ @@ -2131,7 +2153,7 @@ void flecs_bootstrap( 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, (char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ + 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 */ @@ -2280,7 +2302,7 @@ void flecs_filter_apply_iter_flags( #define flecs_signed_ecs_size_t__ true #define flecs_signed_ecs_entity_t__ false -uint64_t _flecs_ito( +uint64_t flecs_ito_( size_t dst_size, bool dst_signed, bool lt_zero, @@ -2289,7 +2311,7 @@ uint64_t _flecs_ito( #ifndef FLECS_NDEBUG #define flecs_ito(T, value)\ - (T)_flecs_ito(\ + (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ (value) < 0,\ @@ -2297,7 +2319,7 @@ uint64_t _flecs_ito( FLECS_CONVERSION_ERR(T, (value))) #define flecs_uto(T, value)\ - (T)_flecs_ito(\ + (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ false,\ @@ -2389,17 +2411,6 @@ void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm); -#define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__) -void _assert_func( - bool cond, - const char *cond_str, - const char *file, - int32_t line, - const char *func); - -void flecs_dump_backtrace( - FILE *stream); - void flecs_colorize_buf( char *msg, bool enable_colors, @@ -2430,59644 +2441,61942 @@ int32_t flecs_search_relation_w_idr( #endif -/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as - * this can severly 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); - - ecs_assert(size == ecs_vec_size(&table->data.records), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == ecs_vec_count(&table->data.records), - ECS_INTERNAL_ERROR, NULL); +/* -- Identifier Component -- */ +static ECS_DTOR(EcsIdentifier, ptr, { + ecs_os_strset(&ptr->value, NULL); +}) - 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; +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; +}) - ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); +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; - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - ecs_assert(table->storage_count == storage_table->type.count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_ids == storage_table->type.array, - ECS_INTERNAL_ERROR, NULL); + src->value = NULL; + src->hash = 0; + src->index_hash = 0; + src->index = 0; + src->length = 0; +}) - int32_t storage_count = table->storage_count; - ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL); +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; - int32_t *storage_map = table->storage_map; - ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, 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); - ecs_id_t *storage_ids = table->storage_ids; - for (i = 0; i < type_count; i ++) { - if (storage_map[i] != -1) { - ecs_assert(ids[i] == storage_ids[storage_map[i]], - ECS_INTERNAL_ERROR, NULL); - } + if (evt == EcsOnSet) { + index = flecs_id_name_index_ensure(world, pair); + } else { + index = flecs_id_name_index_get(world, pair); } + } - ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + int i, count = it->count; - for (i = 0; i < storage_count; i ++) { - ecs_vec_t *column = &table->data.columns[i]; - ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); - int32_t storage_map_id = storage_map[i + type_count]; - ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids[storage_map_id] == storage_ids[i], - ECS_INTERNAL_ERROR, NULL); - } - } else { - ecs_assert(table->storage_count == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_ids == NULL, ECS_INTERNAL_ERROR, NULL); - } + for (i = 0; i < count; i ++) { + EcsIdentifier *cur = &ptr[i]; + uint64_t hash; + ecs_size_t len; + const char *name = cur->value; - 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 (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 (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); + 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; } - } - - ecs_assert((table->_->traversable_count == 0) || - (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); -} -#else -#define flecs_table_check_sanity(table) -#endif - -/* Initialize the storage map for a table. A storage map is an integer array - * that maps type indices to column indices and vice versa. */ -static -void flecs_table_init_storage_map( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!table->storage_table) { - return; - } - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t t, ids_count = type.count; - ecs_id_t *storage_ids = table->storage_ids; - int32_t s, storage_ids_count = table->storage_count; + if (index) { + uint64_t index_hash = cur->index_hash; + ecs_entity_t e = it->entities[i]; - if (!ids_count) { - table->storage_map = NULL; - return; + 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); + } + } } +} - table->storage_map = flecs_walloc_n(world, int32_t, - ids_count + storage_ids_count); - int32_t *t2s = table->storage_map; - int32_t *s2t = &table->storage_map[ids_count]; +/* -- Poly component -- */ - for (s = 0, t = 0; (t < ids_count) && (s < storage_ids_count); ) { - ecs_id_t id = ids[t]; - ecs_id_t storage_id = storage_ids[s]; +static ECS_COPY(EcsPoly, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); +}) - if (id == storage_id) { - t2s[t] = s; - s2t[s] = t; - } else { - t2s[t] = -1; - } +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); + } - /* Ids can never get ahead of storage id, as ids are a superset of the - * storage ids */ - ecs_assert(id <= storage_id, ECS_INTERNAL_ERROR, NULL); + dst->poly = src->poly; + src->poly = NULL; +}) - t += (id <= storage_id); - s += (id == storage_id); +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); } +}) - /* Storage ids is always a subset of ids, so all should be iterated */ - ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); - /* Initialize remainder of type -> storage_type map */ - for (; (t < ids_count); t ++) { - t2s[t] = -1; - } -} +/* -- Builtin triggers -- */ -/* 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) +void flecs_assert_relation_unused( + ecs_world_t *world, + ecs_entity_t rel, + ecs_entity_t property) { - ecs_flags32_t flags = 0; - - if (ti->hooks.ctor) { - flags |= EcsTableHasCtors; - } - if (ti->hooks.on_add) { - flags |= EcsTableHasCtors; + if (world->flags & (EcsWorldInit|EcsWorldFini)) { + return; } - if (ti->hooks.dtor) { - flags |= EcsTableHasDtors; + + 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; + } } - if (ti->hooks.on_remove) { - flags |= EcsTableHasDtors; + + bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); + if (property != EcsUnion) { + in_use |= ecs_id_in_use(world, rel); } - if (ti->hooks.copy) { - flags |= EcsTableHasCopy; + 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); } - if (ti->hooks.move) { - flags |= EcsTableHasMove; - } - return flags; +error: + return; } -/* Initialize array with cached pointers to type info. The type info array has - * an element for each table column. Multiple tables may share the same type - * info array, as long as they have the same components. */ static -void flecs_table_init_type_info( - ecs_world_t *world, - ecs_table_t *table) +bool flecs_set_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_table == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->type_info == NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_record_t *records = table->_->records; - int32_t i, count = table->type.count; - table->type_info = flecs_walloc_n(world, ecs_type_info_t*, count); - - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - - /* All ids in the storage table must be components with type info */ - const ecs_type_info_t *ti = idr->type_info; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - table->flags |= flecs_type_info_flags(ti); - table->type_info[i] = (ecs_type_info_t*)ti; + if (!(idr->flags & flag)) { + idr->flags |= flag; + return true; } + return false; } -/* Find or create the storage table for a table. A storage table only contains - * components, no tags. A table maintains a reference to its storage table as - * this is used for sharing metadata such as the type info array. A storage - * table is found by taking a table type and removing all non-component ids. - * For example, for table - * [Position, Velocity, Npc] - * the storage table would be - * [Position, Velocity] - * assuming that Npc is a tag. - */ static -void flecs_table_init_storage_table( - ecs_world_t *world, - ecs_table_t *table) +bool flecs_unset_id_flag( + ecs_id_record_t *idr, + ecs_flags32_t flag) { - if (table->storage_table) { - return; + if ((idr->flags & flag)) { + idr->flags &= ~flag; + return true; } + return false; +} - ecs_type_t type = table->type; - int32_t i, count = type.count; - ecs_id_t *ids = type.array; - ecs_table_record_t *records = table->_->records; - - ecs_id_t array[FLECS_ID_DESC_MAX]; - ecs_type_t storage_ids = { .array = array }; - if (count > FLECS_ID_DESC_MAX) { - storage_ids.array = flecs_walloc_n(world, ecs_id_t, count); - } +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_table_record_t *tr = &records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_id_t id = ids[i]; + ecs_entity_t e = it->entities[i]; + bool changed = false; - if (idr->type_info != NULL) { - storage_ids.array[storage_ids.count ++] = id; + 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 (storage_ids.count && storage_ids.count != count) { - ecs_table_t *storage_table = flecs_table_find_or_create(world, - &storage_ids); - table->storage_table = storage_table; - table->storage_count = flecs_ito(uint16_t, storage_ids.count); - table->storage_ids = storage_table->type.array; - table->type_info = storage_table->type_info; - table->flags |= storage_table->flags; - storage_table->_->refcount ++; - } else if (storage_ids.count) { - table->storage_table = table; - table->storage_count = flecs_ito(uint16_t, count); - table->storage_ids = type.array; - flecs_table_init_type_info(world, table); - } - if (storage_ids.array != array) { - flecs_wfree_n(world, ecs_id_t, count, storage_ids.array); - } - - if (!table->storage_map) { - flecs_table_init_storage_map(world, table); + if (changed) { + flecs_assert_relation_unused(world, e, prop); + } } } -/* Initialize table column vectors */ -void flecs_table_init_data( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_data_t *storage = &table->data; - int32_t i, count = table->storage_count; - - ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); - ecs_vec_init_t(NULL, &storage->records, ecs_record_t*, 0); - - if (count) { - ecs_vec_t *columns = flecs_wcalloc_n(world, ecs_vec_t, count); - storage->columns = columns; -#ifdef FLECS_DEBUG - ecs_type_info_t **ti = table->type_info; - for (i = 0; i < count; i ++) { - ecs_vec_init(NULL, &columns[i], ti[i]->size, 0); +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; } -#endif } +} - ecs_table__t *meta = table->_; - int32_t sw_count = meta->sw_count; - int32_t bs_count = meta->bs_count; +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); +} - 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); - } - } +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); +} - 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]); - } - } +static +void flecs_register_traversable(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdTraversable, + EcsIdTraversable, 0); } -/* 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; +void flecs_register_tag(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsTag, EcsIdTag, ~EcsIdTag, 0); - int32_t i; + /* 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_id_t id = ids[i]; + ecs_entity_t e = it->entities[i]; - if (id <= EcsLastInternalComponentId) { - table->flags |= EcsTableHasBuiltins; + 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)); } + } +} - 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); +static +void flecs_register_exclusive(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, + EcsIdExclusive, 0); +} - table->flags |= EcsTableHasPairs; +static +void flecs_register_dont_inherit(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsDontInherit, + EcsIdDontInherit, EcsIdDontInherit, 0); +} - 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); +static +void flecs_register_always_override(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsAlwaysOverride, + EcsIdAlwaysOverride, EcsIdAlwaysOverride, 0); +} - 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; +static +void flecs_register_with(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); +} - 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; +static +void flecs_register_union(ecs_iter_t *it) { + flecs_register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); +} - 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; - } - } - } +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); } } -/* 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->column = column; - tr->count = 1; +void flecs_on_symmetric_add_remove(ecs_iter_t *it) { + ecs_entity_t pair = ecs_field_id(it, 1); - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - } else { - tr->count ++; + if (!ECS_HAS_ID_FLAG(pair, PAIR)) { + /* If relationship was not added as a pair, there's nothing to do */ + return; } - ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); + 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); + } + } + } } -/* 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); +static +void flecs_register_symmetric(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; - /* 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. */ + 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); - 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; - } + /* 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} + }); + } +} - /* 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; +static +void flecs_on_component(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsComponent *c = ecs_field(it, EcsComponent, 1); - 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 */ + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; - /* 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; + 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); } } +} - /* 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; +static +void flecs_ensure_module_tag(ecs_iter_t *it) { + ecs_world_t *world = it->world; - 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->column = dst_i; - tr->count = 1; + 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); } - - 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->column = 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; - } +/* -- Iterable mixins -- */ - /* 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); - } +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; +} - /* 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); - } - } - } - } +/* -- Bootstrapping -- */ - 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); +#define flecs_bootstrap_builtin_t(world, table, name)\ + flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) - 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; - } +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); - idr = p_idr->parent; /* (R, *) */ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_column_t *columns = table->data.columns; + ecs_assert(columns != 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->column = dst_i; - tr->count = 0; - } + ecs_record_t *record = flecs_entities_ensure(world, entity); + record->table = table; - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - tr->count ++; - } + int32_t index = flecs_table_append(world, table, entity, record, false, false); + record->row = ECS_ROW_TO_RECORD(index, 0); - last_pair = dst_i; + EcsComponent *component = ecs_vec_first(&columns[0].data); + component[index].size = size; + component[index].alignment = alignment; - /* 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)); + const char *name = &symbol[3]; /* Strip 'Ecs' */ + ecs_size_t symbol_length = ecs_os_strlen(symbol); + ecs_size_t name_length = symbol_length - 3; - flecs_table_append_to_records( - world, table, records, tgt_id, dst_i); - } - } + 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); - /* 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->column = 0; - tr->count = 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->column = first_pair; - tr->count = 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->column = 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->column = 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(uint16_t, dst_record_count); - table->_->records = dst_tr; - - /* 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); - } + 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; +} - /* Claim id record so it stays alive as long as the table exists */ - flecs_id_record_claim(world, idr); +/** 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 event flags */ - table->flags |= idr->flags & EcsIdEventMask; + /* 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)); - if (idr->flags & EcsIdAlwaysOverride) { - table->flags |= EcsTableHasOverrides; - } - } + 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) + }; - flecs_table_init_storage_table(world, table); - flecs_table_init_data(world, table); + ecs_type_t array = { + .array = ids, + .count = 6 + }; - 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); - } + ecs_table_t *result = flecs_table_find_or_create(world, &array); + ecs_data_t *data = &result->data; - if (table->flags & EcsTableHasOnTableCreate) { - flecs_emit(world, world, &(ecs_event_desc_t) { - .ids = &table->type, - .event = EcsOnTableCreate, - .table = table, - .flags = EcsEventTableOnly, - .observable = world - }); - } + /* 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->records, ecs_record_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; } -/* Unregister table from id records */ static -void flecs_table_records_unregister( +void flecs_bootstrap_entity( ecs_world_t *world, - ecs_table_t *table) + ecs_entity_t id, + const char *name, + ecs_entity_t parent) { - 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; + 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(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_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_cache_remove(cache, table_id, &tr->hdr); - flecs_id_record_release(world, (ecs_id_record_t*)cache); + if (!parent || parent == EcsFlecsCore) { + ecs_assert(ecs_lookup_fullpath(world, name) == id, + ECS_INTERNAL_ERROR, NULL); } - - 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 flecs_bootstrap( + ecs_world_t *world) { - (void)world; + ecs_log_push(); - 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; - } -} + ecs_set_name_prefix(world, "Ecs"); -/* 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); - } -} + /* 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); -/* 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_vec_t *column, - ecs_entity_t *entities, - ecs_id_t id, - int32_t row, - int32_t count, - ecs_type_info_t *ti) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - void *ptr = ecs_vec_get(column, ti->size, row); - flecs_invoke_hook( - world, table, count, row, entities, ptr, id, ti, event, callback); -} + /* 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 + }); -/* Construct components */ -static -void flecs_table_invoke_ctor( - ecs_type_info_t *ti, - ecs_vec_t *column, - int32_t row, - int32_t count) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_xtor_t ctor = ti->hooks.ctor; - if (ctor) { - void *ptr = ecs_vec_get(column, ti->size, row); - ctor(ptr, count, ti); - } -} + 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) + }); -/* Destruct components */ -static -void flecs_table_invoke_dtor( - ecs_type_info_t *ti, - ecs_vec_t *column, - int32_t row, - int32_t count) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_type_info_init(world, EcsPoly, { + .ctor = ecs_default_ctor, + .copy = ecs_copy(EcsPoly), + .move = ecs_move(EcsPoly), + .dtor = ecs_dtor(EcsPoly) + }); - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - void *ptr = ecs_vec_get(column, ti->size, row); - dtor(ptr, count, ti); - } -} + flecs_type_info_init(world, EcsIterable, { 0 }); + flecs_type_info_init(world, EcsTarget, { 0 }); -/* 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_type_info_t *ti, - ecs_vec_t *column, - ecs_entity_t *entities, - ecs_id_t id, - int32_t row, - int32_t count, - bool construct) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + /* Create and cache often used id records on world */ + flecs_init_id_records(world); - if (construct) { - flecs_table_invoke_ctor(ti, column, row, count); - } + /* 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); - ecs_iter_action_t on_add = ti->hooks.on_add; - if (on_add) { - flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, - entities, id, row, count, ti); - } -} + /* 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); -/* 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_type_info_t *ti, - ecs_vec_t *column, - ecs_entity_t *entities, - ecs_id_t id, - int32_t row, - int32_t count, - bool dtor) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + /* 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; - ecs_iter_action_t on_remove = ti->hooks.on_remove; - if (on_remove) { - flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, - entities, id, row, count, ti); - } + /* 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 }); - if (dtor) { - flecs_table_invoke_dtor(ti, column, row, count); - } -} + /* 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 + }); -/* 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); + /* Populate core module */ + ecs_set_scope(world, EcsFlecsCore); - ecs_id_t *ids = table->storage_ids; - int32_t ids_count = table->storage_count; - ecs_record_t **records = data->records.array; - ecs_entity_t *entities = data->entities.array; - int32_t i, c, end = row + count; + flecs_bootstrap_tag(world, EcsName); + flecs_bootstrap_tag(world, EcsSymbol); + flecs_bootstrap_tag(world, EcsAlias); - (void)records; + flecs_bootstrap_tag(world, EcsQuery); + flecs_bootstrap_tag(world, EcsObserver); - 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); - } + 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); - /* If table has components with destructors, iterate component columns */ - if (table->flags & EcsTableHasDtors) { - /* Throw up a lock just to be sure */ - table->_->lock = true; + /* Initialize builtin modules */ + ecs_set_name(world, EcsFlecs, "flecs"); + ecs_add_id(world, EcsFlecs, EcsModule); + ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); - /* Run on_remove callbacks first before destructing components */ - for (c = 0; c < ids_count; c++) { - ecs_vec_t *column = &data->columns[c]; - ecs_type_info_t *ti = table->type_info[c]; - 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], ids[c], row, count, ti); - } - } + ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); + ecs_set_name(world, EcsFlecsCore, "core"); + ecs_add_id(world, EcsFlecsCore, EcsModule); - /* Destruct components */ - for (c = 0; c < ids_count; c++) { - flecs_table_invoke_dtor(table->type_info[c], &data->columns[c], - row, count); - } + ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); + ecs_set_name(world, EcsFlecsInternals, "internals"); + ecs_add_id(world, EcsFlecsInternals, EcsModule); - /* 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); - ecs_assert(!e || records[i] == flecs_entities_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); + /* 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; - 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 - records[i]->table = NULL; - records[i]->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) */ - } - } + /* 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); - table->_->lock = false; + /* 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); - /* 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); - ecs_assert(!e || records[i] == flecs_entities_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); + 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_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_assert(!e || records[i] == flecs_entities_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); - records[i]->table = NULL; - records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK; - (void)e; - } - } - } -} + flecs_bootstrap_tag(world, EcsFlatten); + flecs_bootstrap_tag(world, EcsDefaultChildComponent); -/* 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); + /* 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); - if (!data) { - return; - } - - if (do_on_remove) { - flecs_table_notify_on_remove(world, table, data); - } + /* Builtin relationships */ + flecs_bootstrap_tag(world, EcsIsA); + flecs_bootstrap_tag(world, EcsChildOf); + flecs_bootstrap_tag(world, EcsDependsOn); - int32_t count = flecs_table_data_count(data); - if (count) { - flecs_table_dtor_all(world, table, data, 0, count, - update_entity_index, is_delete); - } + /* 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); - /* Sanity check */ - ecs_assert(data->records.count == - data->entities.count, ECS_INTERNAL_ERROR, NULL); + /* 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); - ecs_vec_t *columns = data->columns; - if (columns) { - int32_t c, column_count = table->storage_count; - for (c = 0; c < column_count; c ++) { - /* Sanity check */ - ecs_assert(columns[c].count == data->entities.count, - ECS_INTERNAL_ERROR, NULL); - ecs_vec_fini(&world->allocator, - &columns[c], table->type_info[c]->size); - } - flecs_wfree_n(world, ecs_vec_t, column_count, columns); - data->columns = NULL; - } + /* 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); - 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; - } + /* 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); - 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; - } + /* Create triggers in internals scope */ + ecs_set_scope(world, EcsFlecsInternals); - ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); - ecs_vec_fini_t(&world->allocator, &data->records, ecs_record_t*); + /* Term used to also match prefabs */ + ecs_term_t match_prefab = { + .id = EcsPrefab, + .oper = EcsOptional, + .src.flags = EcsSelf + }; - if (deactivate && count) { - flecs_table_set_empty(world, table); - } + /* 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 + }); - table->_->traversable_count = 0; - table->flags &= ~EcsTableHasTraversable; -} + ecs_observer(world, { + .filter.terms = { + { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete + }); -/* 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); -} + ecs_observer(world, { + .filter.terms = { + { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete_object + }); -/* 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); -} + ecs_observer(world, { + .filter.terms = { + { .id = EcsTraversable, .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_traversable + }); -/* 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); -} + ecs_observer(world, { + .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_exclusive + }); -/* 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); -} + ecs_observer(world, { + .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = flecs_register_symmetric + }); -/* 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); -} + ecs_observer(world, { + .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = flecs_register_dont_inherit + }); -/* 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; + ecs_observer(world, { + .filter.terms = {{ .id = EcsAlwaysOverride, .src.flags = EcsSelf } }, + .events = {EcsOnAdd}, + .callback = flecs_register_always_override + }); - ecs_assert(table->_->refcount == 0, ECS_INTERNAL_ERROR, NULL); + ecs_observer(world, { + .filter.terms = { + { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, + match_prefab + }, + .events = {EcsOnAdd}, + .callback = flecs_register_with + }); - 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 - }); - } - } + ecs_observer(world, { + .filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, + .events = {EcsOnAdd}, + .callback = flecs_register_union + }); - 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(); - } + /* 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 + }); - world->info.empty_table_count -= (ecs_table_count(table) == 0); + /* 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 + }); - /* 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); + /* Set scope back to flecs core */ + ecs_set_scope(world, EcsFlecsCore); - if (!is_root) { - ecs_type_t ids = { - .array = table->type.array, - .count = table->type.count - }; + /* Traversable relationships are always acyclic */ + ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); - flecs_hashmap_remove_w_hash( - &world->store.table_map, &ids, ecs_table_t*, table->_->hash); - } + /* Transitive relationships are always Traversable */ + ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); - flecs_wfree_n(world, int32_t, table->storage_count + 1, table->dirty_state); - flecs_wfree_n(world, int32_t, table->storage_count + table->type.count, - table->storage_map); - flecs_table_records_unregister(world, table); + /* DontInherit components */ + ecs_add_id(world, EcsPrefab, EcsDontInherit); - ecs_table_t *storage_table = table->storage_table; - if (storage_table == table) { - if (table->type_info) { - flecs_wfree_n(world, ecs_type_info_t*, table->storage_count, - table->type_info); - } - } else if (storage_table) { - flecs_table_release(world, storage_table); - } + /* Acyclic/Traversable components */ + ecs_add_id(world, EcsIsA, EcsTraversable); + ecs_add_id(world, EcsDependsOn, EcsTraversable); + ecs_add_id(world, EcsWith, EcsAcyclic); - /* Update counters */ - world->info.table_count --; - world->info.table_record_count -= table->_->record_count; - world->info.table_storage_count -= table->storage_count; - world->info.table_delete_total ++; + /* Transitive relationships */ + ecs_add_id(world, EcsIsA, EcsTransitive); + ecs_add_id(world, EcsIsA, EcsReflexive); - if (!table->storage_count) { - world->info.tag_table_count --; - } else { - world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); - } + /* Exclusive properties */ + ecs_add_id(world, EcsSlotOf, EcsExclusive); + ecs_add_id(world, EcsOneOf, EcsExclusive); + ecs_add_id(world, EcsFlatten, EcsExclusive); - flecs_free_t(&world->allocator, ecs_table__t, table->_); + /* 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); - 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_set_scope(world, 0); - ecs_log_pop_2(); -} + ecs_set_name_prefix(world, NULL); -/* Increase refcount of table. A table will not be freed until its refcount - * reaches zero. Refcounting is primarily used to prevent storage tables from - * being freed while they are still being referred to. - * Tables do not form cyclical dependencies. */ -void flecs_table_claim( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->_->refcount > 0, ECS_INTERNAL_ERROR, NULL); - table->_->refcount ++; - (void)world; + ecs_log_pop(); } -/* Decrease refcount of table */ -bool flecs_table_release( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->_->refcount > 0, ECS_INTERNAL_ERROR, NULL); - - if (--table->_->refcount == 0) { - flecs_table_free(world, table); - return true; - } - - return false; -} +/** + * @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. + */ -/* 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); -} +#include -/* Reset a table to its initial state. */ -void flecs_table_reset( +static +const ecs_entity_t* flecs_bulk_new( 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; - } -} + 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; -/* 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, +flecs_component_ptr_t flecs_get_component_w_index( ecs_table_t *table, - int32_t index) + int32_t column_index, + int32_t row) { - (void)world; - if (table->dirty_state) { - table->dirty_state[index] ++; - } + 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}; } -/* Mark table component dirty */ -void flecs_table_mark_dirty( - ecs_world_t *world, +static +flecs_component_ptr_t flecs_get_component_ptr( + const ecs_world_t *world, ecs_table_t *table, - ecs_entity_t component) + int32_t row, + ecs_id_t id) { - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - if (table->dirty_state) { - int32_t index = ecs_search(world, table->storage_table, component, 0); - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - table->dirty_state[index + 1] ++; + 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}; } -/* 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) +static +void* flecs_get_component( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_id_t id) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!table->dirty_state) { - int32_t column_count = table->storage_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; + return flecs_get_component_ptr(world, table, row, id).ptr; } -/* 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) +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) { - ecs_table__t *dst_meta = dst_table->_; - ecs_table__t *src_meta = src_table->_; - if (!dst_meta && !src_meta) { - return; - } + /* Cycle detected in IsA relationship */ + ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); - 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; + /* Table (and thus entity) does not have component, look for base */ + if (!(table->flags & EcsTableHasIsA)) { + return NULL; } - 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; + /* Exclude Name */ + if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { + return NULL; + } - ecs_id_t *dst_ids = dst_type.array; - ecs_id_t *src_ids = src_type.array; + /* 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); - 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]; + 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; - if (dst_id == src_id) { - ecs_switch_t *src_switch = &src_columns[i_old]; - ecs_switch_t *dst_switch = &dst_columns[i_new]; + do { + ecs_id_t pair = ids[i ++]; + ecs_entity_t base = ecs_pair_second(world, pair); - flecs_switch_ensure(dst_switch, dst_index + count); + ecs_record_t *r = flecs_entities_get(world, base); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - 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); - } + table = r->table; + if (!table) { + continue; + } - 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); + 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)); - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } + return ptr; +error: + return NULL; +} - /* 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); +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; } -/* 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* flecs_find_table_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_table_diff_builder_t *diff) { - ecs_table__t *dst_meta = dst_table->_; - ecs_table__t *src_meta = src_table->_; - if (!dst_meta && !src_meta) { - return; - } + 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; +} - 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; +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; +} - if (!src_column_count && !dst_column_count) { +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_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; + ecs_type_t type = child_table->type; + ecs_data_t *child_data = &child_table->data; - int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; - int32_t offset_old = src_meta ? src_meta->bs_offset : 0; + ecs_entity_t slot_of = 0; + ecs_entity_t *ids = type.array; + int32_t type_count = type.count; - ecs_id_t *dst_ids = dst_type.array; - ecs_id_t *src_ids = src_type.array; + /* Instantiate child table for each instance */ - 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]; + /* Create component array for creating the table */ + ecs_type_t components = { + .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) + }; - if (dst_id == src_id) { - ecs_bitset_t *src_bs = &src_columns[i_old]; - ecs_bitset_t *dst_bs = &dst_columns[i_new]; + void **component_data = ecs_os_alloca_n(void*, type_count + 1); - flecs_bitset_ensure(dst_bs, dst_index + count); + /* Copy in component identifiers. Find the base index in the component + * array, since we'll need this to replace the base with the instance id */ + int j, i, childof_base_index = -1, pos = 0; + for (i = 0; i < type_count; i ++) { + ecs_id_t id = ids[i]; - 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 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; } - - if (clear) { - ecs_assert(count == flecs_bitset_count(src_bs), - ECS_INTERNAL_ERROR, NULL); - flecs_bitset_fini(src_bs); + 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; } - } 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; - } + /* 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; + } + } - /* 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); + /* 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) == base)) { + childof_base_index = pos; } - } -} -/* 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_vec_t *column, - ecs_type_info_t *ti, - int32_t to_add, - int32_t dst_size, - bool construct) -{ - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + 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; + } - int32_t size = ti->size; - int32_t count = column->count; - int32_t src_size = column->size; - int32_t dst_count = count + to_add; - bool can_realloc = dst_size != src_size; - void *result = NULL; + components.array[pos] = id; + pos ++; + } - ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); + /* Table must contain children of base */ + ecs_assert(childof_base_index != -1, 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); + /* 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 ++; + } - /* Create vector */ - ecs_vec_t dst; - ecs_vec_init(&world->allocator, &dst, size, dst_size); - dst.count = dst_count; + components.count = pos; - void *src_buffer = column->array; - void *dst_buffer = dst.array; + /* 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; - /* Move (and construct) existing elements to new vector */ - move_ctor(dst_buffer, src_buffer, count, ti); + 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); - if (construct) { - /* Construct new element(s) */ - result = ECS_ELEM(dst_buffer, size, count); - ctor(result, to_add, ti); + /* Find or create table */ + for (j = 0; j < components.count; j ++) { + i_table = flecs_find_table_add( + world, i_table, components.array[j], &diff); } - /* Free old vector */ - ecs_vec_fini(&world->allocator, column, ti->size); + ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(i_table->type.count == components.count, + ECS_INTERNAL_ERROR, NULL); - *column = 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, size, dst_size); + /* 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 - result = ecs_vec_grow(&world->allocator, column, size, to_add); + /* 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); - 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); + /* 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]; + } + } } - } - ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); + /* 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); + } + } - return result; + /* 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; } -/* Grow all data structures in a table */ -static -int32_t flecs_table_grow_data( +void flecs_instantiate( ecs_world_t *world, + ecs_entity_t base, ecs_table_t *table, - ecs_data_t *data, - int32_t to_add, - int32_t size, - const ecs_entity_t *ids) + int32_t row, + int32_t count) { - 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->storage_count; - - /* Add record to record ptr array */ - ecs_vec_set_size_t(&world->allocator, &data->records, ecs_record_t*, size); - ecs_record_t **r = ecs_vec_last_t(&data->records, ecs_record_t*) + 1; - data->records.count += to_add; - if (data->records.size > size) { - size = data->records.size; - } - - /* 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; - ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL); - - /* 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); + 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_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); - /* Add elements to each column array */ - ecs_vec_t *columns = data->columns; - ecs_type_info_t **type_info = table->type_info; - for (i = 0; i < column_count; i ++) { - ecs_vec_t *column = &columns[i]; - ecs_type_info_t *ti = type_info[i]; - flecs_table_grow_column(world, column, ti, to_add, size, true); - ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL); - flecs_table_invoke_add_hooks(world, table, ti, column, e, table->type.array[i], - cur_count, to_add, false); + 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); + } } +} - 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; +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; - /* 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); - } + for (i = 0; i < id_count; i ++) { + ecs_id_t id = array[i]; - /* 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 (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; + } - /* If the table is monitored indicate that there has been a change */ - flecs_table_mark_table_dirty(world, table, 0); + 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); - if (!(world->flags & EcsWorldReadonly) && !cur_count) { - flecs_table_set_empty(world, table); + int32_t r; + for (r = 0; r < count; r ++) { + flecs_switch_set(sw, row + r, union_case); + } + } } - - /* 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( +void flecs_notify_on_add( ecs_world_t *world, - ecs_type_info_t **type_info, - ecs_vec_t *columns, - int32_t count) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_type_t *added, + ecs_flags32_t flags) { - /* Add elements to each column array */ - int32_t i; - for (i = 0; i < count; i ++) { - ecs_type_info_t *ti = type_info[i]; - ecs_vec_t *column = &columns[i]; - ecs_vec_append(&world->allocator, column, ti->size); + 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 + }); + } } } -/* Append entity to table */ -int32_t flecs_table_append( +void flecs_notify_on_remove( ecs_world_t *world, ecs_table_t *table, - ecs_entity_t entity, - ecs_record_t *record, - 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); + 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); - flecs_table_check_sanity(table); + 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 + }); + } +} - /* 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->storage_count; - ecs_vec_t *columns = table->data.columns; +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; + } - /* 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; + 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; + } - /* Add record ptr to array with record ptrs */ - ecs_record_t **r = ecs_vec_append_t(&world->allocator, - &data->records, ecs_record_t*); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - *r = record; - - /* If the table is monitored indicate that there has been a change */ - flecs_table_mark_table_dirty(world, table, 0); - ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + EcsIdentifier *names = ecs_table_get_pair(world, + dst, EcsIdentifier, EcsName, offset); + ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_type_info_t **type_info = table->type_info; + 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]; - /* Fast path: no switch columns, no lifecycle actions */ - if (!(table->flags & EcsTableIsComplex)) { - flecs_table_fast_append(world, type_info, columns, column_count); - if (!count) { - flecs_table_set_empty(world, table); /* See below */ + 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; } - return count; } +} - ecs_entity_t *entities = data->entities.array; +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, record, ctor, true); + record->table = table; + record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); - /* 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; + 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); - /* Grow component arrays with 1 element */ - int32_t i; - for (i = 0; i < column_count; i ++) { - ecs_vec_t *column = &columns[i]; - ecs_type_info_t *ti = type_info[i]; - flecs_table_grow_column(world, column, ti, 1, size, construct); + return record; +} - ecs_iter_action_t on_add_hook; - if (on_add && (on_add_hook = ti->hooks.on_add)) { - flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, - &entities[count], table->storage_ids[i], count, 1, ti); - } +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(columns[i].size == - data->entities.size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(columns[i].count == - data->entities.count, ECS_INTERNAL_ERROR, NULL); - } + 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); - 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; + /* Append new row to destination table */ + int32_t dst_row = flecs_table_append(world, dst_table, entity, + record, false, false); - /* 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); - } + /* Invoke remove actions for removed components */ + flecs_notify_on_remove( + world, src_table, dst_table, src_row, 1, &diff->removed); - /* 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); - } + /* Copy entity & components from src_table to dst_table */ + flecs_table_move(world, entity, entity, dst_table, dst_row, + src_table, src_row, ctor); - /* 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); - } + /* 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_table_check_sanity(table); + flecs_update_name_index(world, src_table, dst_table, dst_row, 1); - return count; +error: + return; } -/* Delete last operation for tables that don't have any complex logic */ static -void flecs_table_fast_delete_last( - ecs_vec_t *columns, - int32_t column_count) -{ - int i; - for (i = 0; i < column_count; i ++) { - ecs_vec_remove_last(&columns[i]); - } +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); } -/* Delete operation for tables that don't have any complex logic */ +/* 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_table_fast_delete( - ecs_type_info_t **type_info, - ecs_vec_t *columns, - int32_t column_count, - int32_t index) +void flecs_update_component_monitor_w_array( + ecs_world_t *world, + ecs_type_t *ids) { + if (!ids) { + return; + } + int i; - for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = type_info[i]; - ecs_vec_t *column = &columns[i]; - ecs_vec_remove(column, ti->size, index); + 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); } } -/* Delete entity from table */ -void flecs_table_delete( +static +void flecs_update_component_monitors( ecs_world_t *world, - ecs_table_t *table, - int32_t index, - bool destruct) + ecs_type_t *added, + ecs_type_t *removed) { - 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); - - /* Move last record ptr to index */ - ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL); + flecs_update_component_monitor_w_array(world, added); + flecs_update_component_monitor_w_array(world, removed); +} - ecs_record_t **records = data->records.array; - ecs_record_t *record_to_move = records[count]; - records[index] = record_to_move; - ecs_vec_remove_last(&data->records); +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; + } - /* Update record of moved entity in entity index */ - if (index != count) { - 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 (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); } - } - - /* 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); + flecs_journal_end(); + return; } - /* Destruct component data */ - ecs_type_info_t **type_info = table->type_info; - ecs_vec_t *columns = data->columns; - int32_t column_count = table->storage_count; - int32_t i; + if (src_table) { + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_table_traversable_add(dst_table, is_trav); - /* 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); + if (dst_table->type.count) { + flecs_move_entity(world, entity, record, dst_table, diff, + construct, evt_flags); } else { - flecs_table_fast_delete(type_info, columns, column_count, index); + flecs_delete_entity(world, record, diff); + record->table = NULL; } - flecs_table_check_sanity(table); - return; + 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); + } } - ecs_id_t *ids = table->storage_ids; + /* 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); + } - /* 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, type_info[i], &columns[i], - &entity_to_delete, ids[i], index, 1, true); - } - } + 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); + } - flecs_table_fast_delete_last(columns, column_count); +error: + flecs_journal_end(); + return; +} - /* 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_vec_t *column = &columns[i]; - ecs_type_info_t *ti = type_info[i]; - ecs_size_t size = ti->size; - void *dst = ecs_vec_get(column, size, index); - void *src = ecs_vec_last(column, 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, ids[i], index, 1, ti); - } +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); + } - 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); - } + if (!table) { + return entities; + } - ecs_vec_remove_last(column); - } - } else { - flecs_table_fast_delete(type_info, columns, column_count, index); - } + ecs_type_t type = table->type; + if (!type.count) { + return entities; } - /* 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); + ecs_type_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = type.array; + component_array.count = type.count; } - /* 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); + ecs_data_t *data = &table->data; + int32_t row = flecs_table_appendn(world, table, data, count, entities); + + /* Update entity index. */ + int i; + ecs_record_t **records = ecs_vec_first(&data->records); + 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); + records[row + i] = r; } - flecs_table_check_sanity(table); -} + flecs_defer_begin(world, &world->stages[0]); + flecs_notify_on_add(world, table, NULL, row, count, &diff->added, + (component_data == NULL) ? 0 : EcsEventNoOnSet); -/* 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->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; + 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; + } - ecs_vec_t *src_columns = src_table->data.columns; - ecs_vec_t *dst_columns = dst_table->data.columns; + /* 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"); - ecs_type_info_t **dst_type_info = dst_table->type_info; + 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); - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; + 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); + } + }; - if (dst_id == src_id) { - ecs_vec_t *dst_column = &dst_columns[i_new]; - ecs_vec_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = dst_type_info[i_new]; - int32_t size = ti->size; - void *dst = ecs_vec_get(dst_column, size, dst_index); - void *src = ecs_vec_get(src_column, size, src_index); - ecs_os_memcpy(dst, src, size); + 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); } + } - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; + 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; } } -/* Move entity from src to dst table */ -void flecs_table_move( +static +void flecs_add_id_w_record( 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, + ecs_entity_t entity, + ecs_record_t *record, + ecs_id_t id, 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(record != NULL, ECS_INTERNAL_ERROR, 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); + 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. */ +} - 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); +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; } - 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; + 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); - /* 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); + flecs_commit(world, entity, r, dst_table, &diff, true, 0); - ecs_type_info_t **dst_type_info = dst_table->type_info; - ecs_type_info_t **src_type_info = src_table->type_info; + flecs_defer_end(world, stage); +} - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; +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_vec_t *src_columns = src_table->data.columns; - ecs_vec_t *dst_columns = dst_table->data.columns; + 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); - for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; + flecs_commit(world, entity, r, dst_table, &diff, true, 0); - if (dst_id == src_id) { - ecs_vec_t *dst_column = &dst_columns[i_new]; - ecs_vec_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = dst_type_info[i_new]; - int32_t size = ti->size; + flecs_defer_end(world, stage); +} - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *dst = ecs_vec_get(dst_column, size, dst_index); - void *src = ecs_vec_get(src_column, size, src_index); +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}; - 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; - } + 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 (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_type_info[i_new], - &dst_columns[i_new], &dst_entity, dst_id, - dst_index, 1, construct); - } else { - flecs_table_invoke_remove_hooks(world, src_table, src_type_info[i_old], - &src_columns[i_old], &src_entity, src_id, - src_index, 1, use_move_dtor); - } + if (r->table) { + dst = flecs_get_component_ptr( + world, r->table, ECS_RECORD_TO_ROW(r->row), id); + if (dst.ptr) { + return dst; } - - 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_type_info[i_new], - &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1, - construct); - } + /* If entity didn't have component yet, add it */ + flecs_add_id_w_record(world, entity, r, id, true); - for (; (i_old < src_column_count); i_old ++) { - flecs_table_invoke_remove_hooks(world, src_table, src_type_info[i_old], - &src_columns[i_old], &src_entity, src_ids[i_old], - src_index, 1, use_move_dtor); - } + /* Flush commands so the pointer we're fetching is stable */ + flecs_defer_end(world, &world->stages[0]); + flecs_defer_begin(world, &world->stages[0]); - flecs_table_check_sanity(dst_table); - flecs_table_check_sanity(src_table); + 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; } -/* Append n entities to table */ -int32_t flecs_table_appendn( +void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, - int32_t to_add, - const ecs_entity_t *ids) + 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) { - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + int32_t defer = world->stages[0].defer; + if (defer < 0) { + world->stages[0].defer *= -1; + } - 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); + 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); - return result; + world->stages[0].defer = defer; } -/* Set allocated table size */ -void flecs_table_set_size( +void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, - int32_t size) + int32_t row, + int32_t count, + ecs_type_t *ids, + bool owned) { - ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_data_t *data = &table->data; - flecs_table_check_sanity(table); + 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); - int32_t cur_count = flecs_table_data_count(data); + 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); - if (cur_count < size) { - flecs_table_grow_data(world, table, data, 0, size, NULL); - flecs_table_check_sanity(table); + 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 + }); } } -/* Shrink table storage to fit number of entities */ -bool flecs_table_shrink( - ecs_world_t *world, - ecs_table_t *table) +void flecs_record_add_flag( + ecs_record_t *record, + uint32_t flag) { - 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); - ecs_vec_reclaim_t(&world->allocator, &data->records, ecs_record_t*); - - int32_t i, count = table->storage_count; - ecs_type_info_t **type_info = table->type_info; - for (i = 0; i < count; i ++) { - ecs_vec_t *column = &data->columns[i]; - ecs_type_info_t *ti = type_info[i]; - ecs_vec_reclaim(&world->allocator, column, ti->size); + if (flag == EcsEntityIsTraversable) { + if (!(record->row & flag)) { + ecs_table_t *table = record->table; + if (table) { + flecs_table_traversable_add(table, 1); + } + } } - - return has_payload; + record->row |= flag; } -/* Return number of entities in table */ -int32_t flecs_table_data_count( - const ecs_data_t *data) +void flecs_add_flag( + ecs_world_t *world, + ecs_entity_t entity, + uint32_t flag) { - return data ? data->entities.count : 0; + ecs_record_t *record = flecs_entities_get_any(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(record, flag); } -/* Swap operation for switch (union relationship) columns */ -static -void flecs_table_swap_switch_columns( +/* -- Public functions -- */ + +bool ecs_commit( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, ecs_table_t *table, - int32_t row_1, - int32_t row_2) + const ecs_type_t *added, + const ecs_type_t *removed) { - int32_t i = 0, column_count = table->_->sw_count; - if (!column_count) { - return; + 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_switch_t *columns = table->_->sw_columns; + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - for (i = 0; i < column_count; i ++) { - ecs_switch_t *sw = &columns[i]; - flecs_switch_swap(sw, row_1, row_2); + if (added) { + diff.added = *added; } -} - -/* 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; + if (removed) { + diff.removed = *removed; } - ecs_bitset_t *columns = table->_->bs_columns; + ecs_defer_begin(world); + flecs_commit(world, entity, record, table, &diff, true, 0); + ecs_defer_end(world); - for (i = 0; i < column_count; i ++) { - ecs_bitset_t *bs = &columns[i]; - flecs_bitset_swap(bs, row_1, row_2); - } + return src_table != table; +error: + return false; } -/* Swap two rows in a table. Used for table sorting. */ -void flecs_table_swap( +ecs_entity_t ecs_set_with( 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 **records = table->data.records.array; - ecs_record_t *record_ptr_1 = records[row_1]; - ecs_record_t *record_ptr_2 = records[row_2]; - - ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + 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; +} - /* 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); +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; +} - /* 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); - records[row_1] = record_ptr_2; - records[row_2] = record_ptr_1; +ecs_entity_t ecs_new_id( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_table_swap_switch_columns(table, row_1, row_2); - flecs_table_swap_bitset_columns(table, row_1, row_2); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - ecs_vec_t *columns = table->data.columns; - if (!columns) { - flecs_table_check_sanity(table); - return; - } + /* 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_type_info_t **type_info = table->type_info; + 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); - /* 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->storage_count; - for (i = 0; i < column_count; i++) { - ecs_type_info_t* ti = type_info[i]; - temp_buffer_size = ECS_MAX(temp_buffer_size, ti->size); + /* 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); } - void* tmp = ecs_os_alloca(temp_buffer_size); - - /* Swap columns */ - for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = type_info[i]; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - - void *ptr = columns[i].array; - - void *el_1 = ECS_ELEM(ptr, size, row_1); - void *el_2 = ECS_ELEM(ptr, size, row_2); + ecs_assert(!unsafe_world->info.max_id || + ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, + ECS_OUT_OF_RANGE, NULL); - ecs_os_memcpy(tmp, el_1, size); - ecs_os_memcpy(el_1, el_2, size); - ecs_os_memcpy(el_2, tmp, size); - } + flecs_journal(world, EcsJournalNew, entity, 0, 0); - flecs_table_check_sanity(table); + return entity; +error: + return 0; } -/* Merge data from one table column into other table column */ -static -void flecs_table_merge_column( - ecs_world_t *world, - ecs_vec_t *dst, - ecs_vec_t *src, - int32_t size, - int32_t column_size, - ecs_type_info_t *ti) +ecs_entity_t ecs_new_low_id( + ecs_world_t *world) { - int32_t dst_count = dst->count; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - if (!dst_count) { - ecs_vec_fini(&world->allocator, dst, size); - *dst = *src; - src->array = NULL; - src->count = 0; - src->size = 0; - - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = src->count; + /* 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); + } - if (ti) { - flecs_table_grow_column(world, dst, ti, src_count, - column_size, true); - } else { - if (column_size) { - ecs_vec_set_size(&world->allocator, - dst, size, column_size); - } - ecs_vec_set_count(&world->allocator, - dst, size, dst_count + src_count); - } + 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); + } - void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); - void *src_ptr = src->array; + 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); + } - /* Move values into column */ - ecs_move_t move = NULL; - if (ti) { - 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_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_fini(&world->allocator, src, size); - } + return id; +error: + return 0; } -/* Merge storage of two tables. */ -static -void flecs_table_merge_data( +ecs_entity_t ecs_new_w_id( 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) + ecs_id_t id) { - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; - - ecs_type_info_t **dst_type_info = dst_table->type_info; - ecs_type_info_t **src_type_info = src_table->type_info; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!id || ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_vec_t *src = src_data->columns; - ecs_vec_t *dst = dst_data->columns; + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); - ecs_assert(!dst_column_count || dst, ECS_INTERNAL_ERROR, NULL); + ecs_id_t ids[3]; + ecs_type_t to_add = { .array = ids, .count = 0 }; - if (!src_count) { - return; + if (id) { + ids[to_add.count ++] = id; } - /* Merge entities */ - flecs_table_merge_column(world, &dst_data->entities, &src_data->entities, - ECS_SIZEOF(ecs_entity_t), 0, NULL); - 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; - - /* Merge record pointers */ - flecs_table_merge_column(world, &dst_data->records, &src_data->records, - ECS_SIZEOF(ecs_record_t*), 0, NULL); - - for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; - ecs_type_info_t *dst_ti = dst_type_info[i_new]; - int32_t size = dst_ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - - if (dst_id == src_id) { - flecs_table_merge_column(world, &dst[i_new], &src[i_old], size, column_size, dst_ti); - flecs_table_mark_table_dirty(world, dst_table, i_new + 1); - ecs_assert(dst[i_new].size == dst_data->entities.size, - ECS_INTERNAL_ERROR, NULL); + ecs_id_t with = stage->with; + if (with) { + ids[to_add.count ++] = with; + } - i_new ++; - i_old ++; - } else if (dst_id < src_id) { - /* New column, make sure vector is large enough. */ - ecs_vec_t *column = &dst[i_new]; - ecs_vec_set_size(a, column, size, column_size); - ecs_vec_set_count(a, column, size, src_count + dst_count); - flecs_table_invoke_ctor(dst_ti, column, dst_count, src_count); - i_new ++; - } else if (dst_id > src_id) { - /* Old column does not occur in new table, destruct */ - ecs_vec_t *column = &src[i_old]; - ecs_type_info_t *ti = src_type_info[i_old]; - flecs_table_invoke_dtor(ti, column, 0, src_count); - ecs_vec_fini(a, column, ti->size); - i_old ++; + 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; + } - 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); + 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); + } - /* Initialize remaining columns */ - for (; i_new < dst_column_count; i_new ++) { - ecs_vec_t *column = &dst[i_new]; - ecs_type_info_t *ti = dst_type_info[i_new]; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_vec_set_size(a, column, size, column_size); - ecs_vec_set_count(a, column, size, src_count + dst_count); - flecs_table_invoke_ctor(ti, column, dst_count, src_count); - } + 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; + } - /* Destruct remaining columns */ - for (; i_old < src_column_count; i_old ++) { - ecs_vec_t *column = &src[i_old]; - ecs_type_info_t *ti = src_type_info[i_old]; - flecs_table_invoke_dtor(ti, column, 0, src_count); - ecs_vec_fini(a, column, ti->size); - } + flecs_entities_ensure(world, entity); + } + flecs_defer_end(world, stage); - /* Mark entity column as dirty */ - flecs_table_mark_table_dirty(world, dst_table, 0); + return entity; +error: + return 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_entity_t ecs_new_w_table( 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_table_t *table) { - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, 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); - } + flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); + ecs_record_t *r = flecs_entities_get(world, entity); - /* If there is no data to merge, drop out */ - if (!src_data) { - return; - } + 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; +} - if (!dst_data) { - dst_data = &dst_table->data; - if (dst_table == src_table) { - move_data = true; - } - } +#ifdef FLECS_PARSER - 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; - ecs_record_t **src_records = src_data->records.array; +/* 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))){ + if (!ecs_term_is_initialized(&term)) { + break; + } - /* 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; - if (dst_table != src_table) { - record = src_records[i]; - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - record = flecs_entities_ensure(world, src_entities[i]); - } + 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; + } - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); - record->table = dst_table; - } + if (ecs_term_finalize(world, &term)) { + ecs_term_fini(&term); + if (error) { + *error = true; + } + return NULL; + } - /* 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 (!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 (src_count) { - if (!dst_count) { - flecs_table_set_empty(world, dst_table); + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + table = flecs_find_table_add(world, table, term.id, diff); + } + + ecs_term_fini(&term); } - 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); + if (!ptr) { + if (error) { + *error = true; + } + return NULL; + } } - flecs_table_check_sanity(src_table); - flecs_table_check_sanity(dst_table); + return table; } -/* Replace data with other data. Used by snapshots to restore previous state. */ -void flecs_table_replace_data( +/* 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_table_t *table, - ecs_data_t *data) + ecs_entity_t entity, + const char *name, + const char *expr, + bool is_add, + bool replace_and) { - 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); + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } - prev_count = table_data->entities.count; - flecs_table_notify_on_remove(world, table, table_data); - flecs_table_clear_data(world, table, table_data); + if (ecs_term_finalize(world, &term)) { + return; + } - if (data) { - table->data = *data; - } else { - flecs_table_init_data(world, table); - } + 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; + } - int32_t count = ecs_table_count(table); + 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); + } + } - if (!prev_count && count) { - flecs_table_set_empty(world, table); - } else if (prev_count && !count) { - flecs_table_set_empty(world, table); + ecs_term_fini(&term); + } } - - flecs_table_check_sanity(table); } +#endif -/* Internal mechanism for propagating information to tables */ -void flecs_table_notify( +/* 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_table_t *table, - ecs_table_event_t *event) + 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) { - if (world->flags & EcsWorldFini) { - return; - } + 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); - switch(event->kind) { - case EcsTableTriggersForId: - flecs_table_add_trigger_flags(world, table, event->event); - break; - case EcsTableNoTriggersForId: - break; - } -} + /* Find existing table */ + ecs_table_t *src_table = NULL, *table = NULL; + ecs_record_t *r = flecs_entities_get(world, result); + table = r->table; -/* -- Public API -- */ + /* 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); + } -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 ++; + /* 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); } } -} -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); + /* 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); } } -} - -bool ecs_table_has_module( - ecs_table_t *table) -{ - return table->flags & EcsTableHasModule; -} -ecs_vec_t* ecs_table_column_for_id( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - ecs_table_t *storage_table = table->storage_table; - if (!storage_table) { - return NULL; + /* 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 } - ecs_table_record_t *tr = flecs_table_record_get(world, storage_table, id); - if (tr) { - return &table->data.columns[tr->column]; + /* 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]); } - return NULL; -} - -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); -} + /* 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); + } -const ecs_type_t* ecs_table_get_type( - const ecs_table_t *table) -{ - if (table) { - return &table->type; - } else { - return 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); + } } -} -ecs_table_t* ecs_table_get_storage_table( - const ecs_table_t *table) -{ - return table->storage_table; + flecs_table_diff_builder_fini(world, &diff); + return 0; } -int32_t ecs_table_type_to_storage_index( - const ecs_table_t *table, - int32_t index) +/* 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) { - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); - int32_t *storage_map = table->storage_map; - if (storage_map) { - return storage_map[index]; - } -error: - return -1; -} + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; -int32_t ecs_table_storage_to_type_index( - const ecs_table_t *table, - int32_t index) -{ - ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t offset = table->type.count; - return table->storage_map[offset + index]; -error: - return -1; -} + /* 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)); + } -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; + if (with) { + ecs_add_id(world, entity, with); } } - 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->type.count, ECS_INVALID_PARAMETER, NULL); - ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t storage_index = table->storage_map[index]; - if (storage_index == -1) { - return NULL; + /* 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); + } } - void *result = table->data.columns[storage_index].array; - if (offset) { - ecs_size_t size = table->type_info[storage_index]->size; - result = ECS_ELEM(result, size, offset); + /* 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 } - return result; -error: - return NULL; -} + int32_t thread_count = ecs_get_stage_count(world); -size_t ecs_table_get_column_size( - const ecs_table_t *table, - int32_t index) -{ - ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); - ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); - - int32_t storage_index = table->storage_map[index]; - if (storage_index == -1) { - return 0; + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } - return flecs_ito(size_t, table->type_info[storage_index]->size); -error: - return 0; + /* 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); + } + } + } } -int32_t ecs_table_get_index( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) +ecs_entity_t ecs_entity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc) { - 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_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_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; + 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 = "."; } - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return -1; + 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; + } } - return tr->column; -error: - return -1; -} + const char *root_sep = desc->root_sep; + bool flecs_new_entity = false; + bool name_assigned = false; -bool ecs_table_has_id( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - return ecs_table_get_index(world, table, id) != -1; -} + /* 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; + } + } + } -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); + /* 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); + } + } - world = ecs_get_world(world); + result = ecs_lookup_path_w_sep( + world, scope, name, sep, root_sep, false); + if (result) { + name_assigned = true; + } + } - int32_t index = ecs_table_get_index(world, table, id); - if (index == -1) { - return NULL; - } + 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); - return ecs_table_get_column(table, index, offset); -error: - return NULL; -} + 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); + } + } + } + } -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); + ecs_assert(name_assigned == ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName), + ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + 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 flecs_relation_depth(world, rel, table); + return result; error: - return -1; + return 0; } -void ecs_table_swap_rows( - ecs_world_t* world, - ecs_table_t* table, - int32_t row_1, - int32_t row_2) +const ecs_entity_t* ecs_bulk_init( + ecs_world_t *world, + const ecs_bulk_desc_t *desc) { - flecs_table_swap(world, table, row_1, row_2); -} + 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); -int32_t flecs_table_observed_count( - const ecs_table_t *table) -{ - return table->_->traversable_count; -} + const ecs_entity_t *entities = desc->entities; + int32_t count = desc->count; -void* ecs_record_get_column( - const ecs_record_t *r, - int32_t column, - size_t c_size) -{ - (void)c_size; - ecs_table_t *table = r->table; + 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); - ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL); - ecs_type_info_t *ti = table->type_info[column]; - ecs_vec_t *c = &table->data.columns[column]; - ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = 0; + ecs_id_t id; + while ((id = desc->ids[i])) { + table = flecs_find_table_add(world, table, id, &diff); + i ++; + } - ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size, - ECS_INVALID_PARAMETER, NULL); + 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); + } - return ecs_vec_get(c, ti->size, ECS_RECORD_TO_ROW(r->row)); + 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; } -ecs_record_t* ecs_record_find( - const ecs_world_t *world, - ecs_entity_t entity) +static +void flecs_check_component( + ecs_world_t *world, + ecs_entity_t result, + const EcsComponent *ptr, + ecs_size_t size, + ecs_size_t alignment) { - 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; + 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); } -error: - return NULL; } -/** - * @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. - */ +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; + } + } -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_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); -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) + bool new_component = true; + if (!result) { + result = ecs_new_low_id(world); + } else { + ecs_ensure(world, result); + new_component = ecs_has(world, result, EcsComponent); } -}; -ecs_mixins_t ecs_stage_t_mixins = { - .type_name = "ecs_stage_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_stage_t, world) + 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_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_modified(world, result, EcsComponent); -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) + 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); } -}; -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) + if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { + world->info.last_component_id = result + 1; } -}; - -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); + /* Ensure components cannot be deleted */ + ecs_add_pair(world, result, EcsOnDelete, EcsPanic); - 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; + 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); - /* Object has mixin, return its address */ - return ECS_OFFSET(hdr, offset); + return result; +error: + return 0; } -void* _ecs_poly_init( - ecs_poly_t *poly, - int32_t type, - ecs_size_t size, - ecs_mixins_t *mixins) +const ecs_entity_t* ecs_bulk_new_w_id( + ecs_world_t *world, + ecs_id_t id, + int32_t count) { - 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; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - return poly; -} + const ecs_entity_t *ids; + if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { + return ids; + } -void _ecs_poly_fini( - ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - (void)type; + 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_header_t *hdr = poly; + 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); - /* 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; + return ids; +error: + return NULL; } -EcsPoly* _ecs_poly_bind( +void ecs_clear( ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) + ecs_entity_t entity) { - /* 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); - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - /* Never defer creation of a poly object */ - bool deferred = false; - if (ecs_is_deferred(world)) { - deferred = true; - ecs_defer_suspend(world); + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_clear(stage, entity)) { + return; } - /* 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); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - if (deferred) { - ecs_defer_resume(world); - } + ecs_table_t *table = r->table; + if (table) { + ecs_table_diff_t diff = { + .removed = table->type + }; - return result; -} + flecs_delete_entity(world, r, &diff); + r->table = NULL; -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); -} + if (r->row & EcsEntityIsTraversable) { + flecs_table_traversable_add(table, -1); + } + } -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); + flecs_defer_end(world, stage); +error: + return; } -ecs_poly_t* _ecs_poly_get( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) +static +void flecs_throw_invalid_delete( + ecs_world_t *world, + ecs_id_t id) { - const EcsPoly *p = _ecs_poly_bind_get(world, entity, tag); - if (p) { - return p->poly; + char *id_str = NULL; + if (!(world->flags & EcsWorldQuit)) { + id_str = ecs_id_str(world, id); + ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); } - return NULL; -} - -#define assert_object(cond, file, line, type_name)\ - _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\ - assert(cond) - -#ifndef FLECS_NDEBUG -void* _ecs_poly_assert( - const ecs_poly_t *poly, - int32_t type, - const char *file, - int32_t line) -{ - assert_object(poly != NULL, file, line, 0); - - const ecs_header_t *hdr = poly; - const char *type_name = hdr->mixins->type_name; - assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name); - assert_object(hdr->type == type, file, line, type_name); - return (ecs_poly_t*)poly; +error: + ecs_os_free(id_str); } -#endif -bool _ecs_poly_is( - const ecs_poly_t *poly, - int32_t type) +static +void flecs_marked_id_push( + ecs_world_t *world, + ecs_id_record_t* idr, + ecs_entity_t action, + bool delete_id) { - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, + &world->store.marked_ids, ecs_marked_id_t); - const ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - return hdr->type == type; -} + m->idr = idr; + m->id = idr->id; + m->action = action; + m->delete_id = delete_id; -ecs_iterable_t* ecs_get_iterable( - const ecs_poly_t *poly) -{ - return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); + flecs_id_record_claim(world, idr); } -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *poly) -{ - return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); -} +static +void flecs_id_mark_for_delete( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_entity_t action, + bool delete_id); -const ecs_world_t* ecs_get_world( - const ecs_poly_t *poly) +static +void flecs_targets_mark_for_delete( + ecs_world_t *world, + ecs_table_t *table) { - if (((ecs_header_t*)poly)->type == ecs_world_t_magic) { - return poly; - } - return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); -} + ecs_id_record_t *idr; + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + ecs_record_t **records = ecs_vec_first(&table->data.records); + int32_t i, count = ecs_vec_count(&table->data.entities); + for (i = 0; i < count; i ++) { + ecs_record_t *r = records[i]; + if (!r) { + continue; + } -ecs_entity_t ecs_get_entity( - const ecs_poly_t *poly) -{ - return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); -} + /* 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_poly_dtor_t* ecs_get_dtor( - const ecs_poly_t *poly) -{ - return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); + 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); + } + } + } } -/** - * @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 { - 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) +bool flecs_id_is_delete_target( + ecs_id_t id, + ecs_entity_t action) { - ecs_check(column_index < table->storage_count, ECS_NOT_A_COMPONENT, NULL); - ecs_type_info_t *ti = table->type_info[column_index]; - ecs_vec_t *column = &table->data.columns[column_index]; - return (flecs_component_ptr_t){ - .ti = ti, - .ptr = ecs_vec_get(column, ti->size, row) - }; -error: - return (flecs_component_ptr_t){0}; + 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 -flecs_component_ptr_t flecs_get_component_ptr( - const ecs_world_t *world, +ecs_entity_t flecs_get_delete_action( ecs_table_t *table, - int32_t row, - ecs_id_t id) + ecs_table_record_t *tr, + ecs_entity_t action, + bool delete_target) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + 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 (!table->storage_table) { - ecs_check(ecs_search(world, table, id, 0) == -1, - ECS_NOT_A_COMPONENT, NULL); - return (flecs_component_ptr_t){0}; - } + /* 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; + } - ecs_table_record_t *tr = flecs_table_record_get( - world, table->storage_table, id); - if (!tr) { - ecs_check(ecs_search(world, table, id, 0) == -1, - ECS_NOT_A_COMPONENT, NULL); - return (flecs_component_ptr_t){0}; + 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 flecs_get_component_w_index(table, tr->column, row); -error: - return (flecs_component_ptr_t){0}; + return result; } static -void* flecs_get_component( - const ecs_world_t *world, - ecs_table_t *table, - int32_t row, +void flecs_update_monitors_for_delete( + ecs_world_t *world, ecs_id_t id) { - return flecs_get_component_ptr(world, table, row, id).ptr; + flecs_update_component_monitors(world, NULL, &(ecs_type_t){ + .array = (ecs_id_t[]){id}, + .count = 1 + }); } -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) +static +void flecs_id_mark_for_delete( + ecs_world_t *world, + ecs_id_record_t *idr, + ecs_entity_t action, + bool delete_id) { - /* 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; + if (idr->flags & EcsIdMarkedForDelete) { + return; } - /* Exclude Name */ - if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { - return NULL; - } + idr->flags |= EcsIdMarkedForDelete; + flecs_marked_id_push(world, idr, action, delete_id); - /* Table should always be in the table index for (IsA, *), otherwise the - * HasBase flag should not have been set */ - const 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_id_t id = idr->id; - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; - void *ptr = NULL; + bool delete_target = flecs_id_is_delete_target(id, action); - do { - ecs_id_t pair = ids[i ++]; - ecs_entity_t base = ecs_pair_second(world, pair); + /* 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_record_t *r = flecs_entities_get(world, base); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, + delete_target); - table = r->table; - if (!table) { - continue; + /* 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); + } } + } - const ecs_table_record_t *tr = NULL; - - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - tr = flecs_id_record_get_table(table_index, storage_table); - } else { - ecs_check(!ecs_owns_id(world, base, id), - ECS_NOT_A_COMPONENT, NULL); + /* 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; } + } - if (!tr) { - ptr = flecs_get_base_component(world, table, id, table_index, - recur_depth + 1); + /* 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 { - int32_t row = ECS_RECORD_TO_ROW(r->row); - ptr = flecs_get_component_w_index(table, tr->column, row).ptr; + ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + while ((cur = cur->second.next)) { + flecs_update_monitors_for_delete(world, cur->id); + } } - } while (!ptr && (i < end)); - - return ptr; -error: - return NULL; + } } static -void flecs_instantiate_slot( +bool flecs_on_delete_mark( 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) + ecs_id_t id, + ecs_entity_t action, + bool delete_id) { - 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; - } + 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; + } - 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); + 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); } } -error: - return; -} + if (action == EcsPanic) { + /* This id is protected from deletion */ + flecs_throw_invalid_delete(world, id); + return false; + } -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; -} + flecs_id_mark_for_delete(world, idr, action, delete_id); -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; + return true; } 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) +void flecs_remove_from_table( + ecs_world_t *world, + ecs_table_t *table) { - if (!ecs_table_count(child_table)) { - return; - } - - ecs_type_t type = child_table->type; - ecs_data_t *child_data = &child_table->data; + 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; - ecs_entity_t slot_of = 0; - ecs_entity_t *ids = type.array; - int32_t type_count = type.count; + /* 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; - /* Instantiate child table for each instance */ + if (!(tr = flecs_id_record_get_table(idr, dst_table))) { + continue; + } - /* Create component array for creating the table */ - ecs_type_t components = { - .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) - }; + t = tr->index; - 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]; + 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); + } - /* 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; - } - } + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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; + 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(); } - } - - /* 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) == base)) { - childof_base_index = pos; - } - int32_t storage_index = ecs_table_type_to_storage_index(child_table, i); - if (storage_index != -1) { - ecs_vec_t *column = &child_data->columns[storage_index]; - component_data[pos] = ecs_vec_first(column); - } else { - component_data[pos] = NULL; + flecs_table_merge(world, dst_table, table, + &dst_table->data, &table->data); } - - 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 ++; - } + flecs_table_diff_builder_fini(world, &diff); +} - components.count = pos; +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; - /* 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; + /* 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); + } + } + } + } - 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); + /* 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]); + } - /* Find or create table */ - for (j = 0; j < components.count; j ++) { - i_table = flecs_find_table_add( - world, i_table, components.array[j], &diff); + /* 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); } - 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); + /* 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; } -#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 + } while (true); - /* 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); + return true; +} - /* 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]; +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; } } - } - /* 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); - } - } + ecs_id_record_t *idr = ids[i].idr; + bool delete_id = ids[i].delete_id; - /* 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; -} + flecs_id_record_release_tables(world, idr); -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; - } + /* 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; - 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); + /* 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_set_union( +void flecs_on_delete( ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_type_t *ids) + ecs_id_t id, + ecs_entity_t action, + bool delete_id) { - ecs_id_t *array = ids->array; - int32_t i, id_count = ids->count; + /* 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); - for (i = 0; i < id_count; i ++) { - ecs_id_t id = array[i]; + /* Make sure we're evaluating a consistent list of non-empty tables */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - 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; - } + /* Collect all ids that need to be deleted */ + flecs_on_delete_mark(world, id, action, delete_id); - const 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->column - table->_->sw_offset; - ecs_switch_t *sw = &table->_->sw_columns[column]; - ecs_entity_t union_case = 0; - union_case = ECS_PAIR_SECOND(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(); - int32_t r; - for (r = 0; r < count; r ++) { - flecs_switch_set(sw, row + r, union_case); - } + /* 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(); } } -static -void flecs_notify_on_add( +void ecs_delete_with( 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_id_t id) { - ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); - if (added->count) { - ecs_flags32_t table_flags = table->flags; + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { + return; + } - if (table_flags & EcsTableHasUnion) { - flecs_set_union(world, table, row, count, added); - } + flecs_on_delete(world, id, EcsDelete, false); + flecs_defer_end(world, stage); - 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 - }); - } - } + flecs_journal_end(); } -void flecs_notify_on_remove( +void ecs_remove_all( 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_id_t id) { - ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, 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 - }); + 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(); } -static -void flecs_update_name_index( +void ecs_delete( ecs_world_t *world, - ecs_table_t *src, - ecs_table_t *dst, - int32_t offset, - int32_t count) + ecs_entity_t entity) { - 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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - 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. */ + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_delete(stage, entity)) { 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]; + ecs_record_t *r = flecs_entities_try(world, entity); + if (r) { + flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); - uint64_t index_hash = name->index_hash; - if (index_hash) { - flecs_name_index_remove(src_index, e, index_hash); + 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); } - 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; + 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; } -static -ecs_record_t* flecs_new_entity( +void ecs_add_id( 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_id_t id) { - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t row = flecs_table_append(world, table, entity, record, 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; + 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; } -static -void flecs_move_entity( +void ecs_remove_id( 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_id_t id) { - 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(world != NULL, ECS_INVALID_PARAMETER, 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, - record, 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); - + 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; } -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( +void ecs_override_id( 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_id_t id) { - 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); - } - + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_add_id(world, entity, ECS_OVERRIDE | id); error: - flecs_journal_end(); return; } -static -const ecs_entity_t* flecs_bulk_new( +ecs_entity_t ecs_clone( 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) + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) { - int32_t sparse_count = 0; - if (!entities) { - sparse_count = flecs_entities_count(world); - entities = flecs_entities_new_ids(world, count); - } + 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); - if (!table) { - return entities; + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (!dst) { + dst = ecs_new_id(world); } - ecs_type_t type = table->type; - if (!type.count) { - return entities; + if (flecs_defer_clone(stage, dst, src, copy_value)) { + return dst; } - ecs_type_t component_array = { 0 }; - if (!component_ids) { - component_ids = &component_array; - component_array.array = type.array; - component_array.count = type.count; + 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_data_t *data = &table->data; - int32_t row = flecs_table_appendn(world, table, data, count, entities); + ecs_type_t src_type = src_table->type; + ecs_table_diff_t diff = { .added = src_type }; + ecs_record_t *dst_r = flecs_entities_get(world, dst); + flecs_new_entity(world, dst, dst_r, src_table, &diff, true, true); + int32_t row = ECS_RECORD_TO_ROW(dst_r->row); - /* Update entity index. */ - int i; - ecs_record_t **records = ecs_vec_first(&data->records); - 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); - records[row + i] = r; + if (copy_value) { + flecs_table_move(world, dst, src, src_table, + row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); + int32_t i, count = src_table->column_count; + for (i = 0; i < count; i ++) { + ecs_type_t type = { + .array = &src_table->data.columns[i].id, + .count = 1 + }; + flecs_notify_on_set(world, src_table, row, 1, &type, true); + } } - 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; - ecs_table_t *storage_table = table->storage_table; - for (c_i = 0; c_i < component_ids->count; c_i ++) { - void *src_ptr = component_data[c_i]; - if (!src_ptr) { - continue; - } +done: + flecs_defer_end(world, stage); + return dst; +error: + return 0; +} - /* 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, storage_table, id); - ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, - "id is not a component"); - ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, - "ids cannot be wildcards"); +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); - int32_t index = tr->column; - ecs_type_info_t *ti = table->type_info[index]; - ecs_vec_t *column = &table->data.columns[index]; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *ptr = ecs_vec_get(column, size, row); + world = ecs_get_world(world); - 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); - } - }; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_notify_on_set(world, table, row, count, NULL, true); + ecs_table_t *table = r->table; + if (!table) { + return NULL; } - flecs_defer_end(world, &world->stages[0]); - - if (row_out) { - *row_out = row; + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; } - if (sparse_count) { - entities = flecs_entities_ids(world); - return &entities[sparse_count]; + 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 { - return entities; + ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); } -} - -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. */ + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_get_component_w_index(table, tr->column, row).ptr; +error: + return NULL; } -static -void flecs_add_id( +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_add(stage, entity, id)) { - return; + if (flecs_defer_cmd(stage)) { + return flecs_defer_set( + world, stage, EcsOpMut, entity, id, 0, NULL, true); } 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); + 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; } -static -void flecs_remove_id( +void* ecs_get_mut_modified_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); + 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_commit(world, entity, r, dst_table, &diff, true, 0); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); - flecs_defer_end(world, stage); + return flecs_defer_set( + world, stage, EcsOpSet, entity, id, 0, NULL, true); +error: + return NULL; } static -flecs_component_ptr_t flecs_get_mut( - ecs_world_t *world, +ecs_record_t* flecs_access_begin( + ecs_world_t *stage, ecs_entity_t entity, - ecs_entity_t id, - ecs_record_t *r) + bool write) { - flecs_component_ptr_t dst = {0}; + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - 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); + 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); - if (r->table) { - dst = flecs_get_component_ptr( - world, r->table, ECS_RECORD_TO_ROW(r->row), id); - if (dst.ptr) { - return dst; - } + ecs_table_t *table; + if (!(table = r->table)) { + return NULL; } - /* If entity didn't have component yet, add it */ - flecs_add_id_w_record(world, entity, r, id, true); + int32_t count = ecs_os_ainc(&table->_->lock); + (void)count; + if (write) { + ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); + } - /* Flush commands so the pointer we're fetching is stable */ - flecs_defer_end(world, &world->stages[0]); - flecs_defer_begin(world, &world->stages[0]); + 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); - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(r->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); - dst = flecs_get_component_ptr( - world, r->table, ECS_RECORD_TO_ROW(r->row), id); error: - return dst; + return; } -void flecs_invoke_hook( +ecs_record_t* ecs_write_begin( 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) + ecs_entity_t entity) { - ecs_assert(ti->size != 0, ECS_INVALID_PARAMETER, NULL); + return flecs_access_begin(world, entity, true); +} - 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_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); +void ecs_write_end( + ecs_record_t *r) +{ + flecs_access_end(r, true); } -void flecs_notify_on_set( +const ecs_record_t* ecs_read_begin( ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_type_t *ids, - bool owned) + ecs_entity_t entity) { - ecs_data_t *data = &table->data; + return flecs_access_begin(world, entity, false); +} - 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); +void ecs_read_end( + const ecs_record_t *r) +{ + flecs_access_end(r, false); +} - ecs_type_t local_ids; - if (!ids) { - local_ids.array = table->storage_ids; - local_ids.count = table->storage_count; - ids = &local_ids; +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; } - if (owned) { - ecs_table_t *storage_table = table->storage_table; - 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, - storage_table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - int32_t column = tr->column; - const ecs_type_info_t *ti = table->type_info[column]; - ecs_iter_action_t on_set = ti->hooks.on_set; - if (on_set) { - ecs_vec_t *c = &table->data.columns[column]; - void *ptr = ecs_vec_get(c, ti->size, row); - flecs_invoke_hook(world, table, count, row, entities, ptr, id, - ti, EcsOnSet, on_set); - } - } - } + return ecs_vec_get_t(&table->data.entities, ecs_entity_t, + ECS_RECORD_TO_ROW(record->row))[0]; +error: + return 0; +} - /* 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 - }); - } +const void* ecs_record_get_id( + 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); } -void flecs_record_add_flag( - ecs_record_t *record, - uint32_t flag) +bool ecs_record_has_id( + ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t id) { - if (flag == EcsEntityIsTraversable) { - if (!(record->row & flag)) { - ecs_table_t *table = record->table; - if (table) { - flecs_table_traversable_add(table, 1); - } - } + const ecs_world_t *world = ecs_get_world(stage); + if (r->table) { + return ecs_table_has_id(world, r->table, id); } - record->row |= flag; + return false; } -void flecs_add_flag( - ecs_world_t *world, - ecs_entity_t entity, - uint32_t flag) +void* ecs_record_get_mut_id( + ecs_world_t *stage, + ecs_record_t *r, + ecs_id_t id) { - ecs_record_t *record = flecs_entities_get_any(world, entity); - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_record_add_flag(record, flag); + const ecs_world_t *world = ecs_get_world(stage); + return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } -/* -- Public functions -- */ - -bool ecs_commit( - ecs_world_t *world, +ecs_ref_t ecs_ref_init_id( + const 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_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); + + world = ecs_get_world(world); - ecs_table_t *src_table = NULL; - if (!record) { - record = flecs_entities_get(world, entity); - src_table = record->table; - } + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_check(record != NULL, ECS_INVALID_PARAMETER, + "cannot create ref for empty entity"); - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_ref_t result = { + .entity = entity, + .id = id, + .record = record + }; - if (added) { - diff.added = *added; - } - if (removed) { - diff.added = *removed; + ecs_table_t *table = record->table; + if (table) { + result.tr = flecs_table_record_get(world, table, id); } - - flecs_commit(world, entity, record, table, &diff, true, 0); - return src_table != table; + return result; error: - return false; + return (ecs_ref_t){0}; } -ecs_entity_t ecs_set_with( - ecs_world_t *world, - ecs_id_t id) +void ecs_ref_update( + const ecs_world_t *world, + ecs_ref_t *ref) { 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_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_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; + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { + return; + } + + ecs_table_record_t *tr = ref->tr; + if (!tr || tr->hdr.table != 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); + } error: - return 0; + return; } -ecs_entity_t ecs_new_id( - ecs_world_t *world) +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); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { + return 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, - * since it is thread safe (uses atomic inc when in threading mode) */ - ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - 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); + ecs_table_record_t *tr = ref->tr; + if (!tr || tr->hdr.table != table) { + tr = ref->tr = flecs_table_record_get(world, table, id); + if (!tr) { + return 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(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } - 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; + 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 0; + return NULL; } -ecs_entity_t ecs_new_low_id( - ecs_world_t *world) +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"); - /* 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_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_stage_t *stage = flecs_stage_from_world(&world); - 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 (flecs_defer_cmd(stage)) { + return flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, + true); } - 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_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); - ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); + void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - return id; -error: - return 0; + return ptr; +error: + return NULL; } -ecs_entity_t ecs_new_w_id( +static +void flecs_modified_id_if( ecs_world_t *world, + ecs_entity_t entity, 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 }; + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - if (id) { - ids[to_add.count ++] = id; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_id_t with = stage->with; - if (with) { - ids[to_add.count ++] = with; + if (flecs_defer_modified(stage, entity, id)) { + return; } - ecs_entity_t scope = stage->scope; - if (scope) { - if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { - ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); - } + 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; } - 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; - } + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); - flecs_entities_ensure(world, entity); - } + flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); - - return entity; error: - return 0; + return; } -ecs_entity_t ecs_new_w_table( +void ecs_modified_id( ecs_world_t *world, - ecs_table_t *table) + 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); - flecs_stage_from_world(&world); - ecs_entity_t entity = ecs_new_id(world); 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); - ecs_table_diff_t table_diff = { .added = table->type }; - flecs_new_entity(world, entity, r, table, &table_diff, true, true); - return entity; + flecs_table_mark_dirty(world, table, id); + flecs_defer_end(world, stage); error: - return 0; + return; } -#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( +void flecs_copy_ptr_w_id( 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) + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void *ptr) { - const char *ptr = expr; - if (ptr) { - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - 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 (flecs_defer_cmd(stage)) { + flecs_defer_set(world, stage, EcsOpSet, entity, id, + flecs_utosize(size), ptr, false); + return; + } - if (term.oper == EcsAnd || !replace_and) { - /* Regular AND expression */ - table = flecs_find_table_add(world, table, term.id, diff); - } + 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); - ecs_term_fini(&term); + 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); + } - if (!ptr) { - if (error) { - *error = true; - } - return NULL; - } + 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); } - return table; + flecs_defer_end(world, stage); +error: + return; } -/* 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( +void flecs_move_ptr_w_id( ecs_world_t *world, + ecs_stage_t *stage, ecs_entity_t entity, - const char *name, - const char *expr, - bool is_add, - bool replace_and) + ecs_id_t id, + size_t size, + void *ptr, + ecs_cmd_kind_t cmd_kind) { - const char *ptr = expr; - if (ptr) { - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; - } + if (flecs_defer_cmd(stage)) { + flecs_defer_set(world, stage, cmd_kind, entity, id, + flecs_utosize(size), ptr, false); + return; + } - if (ecs_term_finalize(world, &term)) { - 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); - 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; - } + const ecs_type_info_t *ti = dst.ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_move_t move; + if (cmd_kind != EcsOpEmplace) { + /* 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)); + } - 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); - } - } + flecs_table_mark_dirty(world, r->table, id); - ecs_term_fini(&term); + if (cmd_kind == EcsOpSet) { + 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; } -#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_entity_t ecs_set_id( 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) + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) { - 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; + 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); - /* 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); - } + ecs_stage_t *stage = flecs_stage_from_world(&world); - /* 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); + if (!entity) { + entity = ecs_new_id(world); + ecs_entity_t scope = stage->scope; + if (scope) { + ecs_add_pair(world, entity, EcsChildOf, scope); } } - /* 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); - } + /* 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 --; } - /* 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 + 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); } - /* 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]); + 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); - /* 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); + /* 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; } - 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); - } + 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); } - flecs_table_diff_builder_fini(world, &diff); - return 0; + 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; } -/* 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, +bool ecs_has_id( + const 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) + ecs_id_t id) { - const char *sep = desc->sep; - const char *root_sep = desc->root_sep; + 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); - /* 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)); - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - if (with) { - ecs_add_id(world, entity, with); - } + 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; } - /* 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); + 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; } } - /* 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 + if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { + return false; } - 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); + ecs_table_record_t *tr; + column = ecs_search_relation(world, table, 0, id, + EcsIsA, 0, 0, 0, &tr); + if (column == -1) { + return false; } - /* 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); - } + 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; } -ecs_entity_t ecs_entity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc) +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(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(rel != 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; + world = ecs_get_world(world); - const char *name = desc->name; - const char *sep = desc->sep; - if (!sep) { - sep = "."; + 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; } - 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; + 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); + } - name = NULL; - result = id; + } + + 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); } } - const char *root_sep = desc->root_sep; - bool flecs_new_entity = false; - bool name_assigned = false; + if (index >= tr->count) { + index -= tr->count; + goto look_in_base; + } - /* 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; + 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; } } } - /* 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); - } - } +not_found: +error: + return 0; +} - result = ecs_lookup_path_w_sep( - world, scope, name, sep, root_sep, false); - if (result) { - name_assigned = true; - } - } +ecs_entity_t ecs_get_parent( + const ecs_world_t *world, + ecs_entity_t entity) +{ + return ecs_get_target(world, entity, EcsChildOf, 0); +} - 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); +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 { - /* Make sure provided id is either alive or revivable */ - ecs_ensure(world, result); + entity = 0; /* Don't return entity if id was not found */ - 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 */ - path = ecs_get_path_w_sep(world, scope, result, sep, ""); - } - if (path) { - if (ecs_os_strcmp(path, name)) { - /* Mismatching name */ - ecs_err("existing entity '%s' is initialized with " - "conflicting name '%s'", path, name); - ecs_os_free(path); - return 0; + 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; } - 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); + if (subject == 0) { + return entity; } else { - if (flecs_traverse_add(world, result, name, desc, - scope, with, flecs_new_entity, name_assigned)) - { - return 0; - } + return subject; } - - return result; error: return 0; } -const ecs_entity_t* ecs_bulk_init( - ecs_world_t *world, - const ecs_bulk_desc_t *desc) +int32_t ecs_get_depth( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel) { - 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); + ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); - const ecs_entity_t *entities = desc->entities; - int32_t count = desc->count; + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return ecs_table_get_depth(world, table, rel); + } - 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_id_t*)desc->ids; - ids.count = i; + return 0; +error: + return -1; +} - 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); +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); } - 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; + return ecs_vec_get_t(&world->store.depth_ids, ecs_entity_t, depth)[0]; } static -void flecs_check_component( +int32_t flecs_depth_for_id( ecs_world_t *world, - ecs_entity_t result, - const EcsComponent *ptr, - ecs_size_t size, - ecs_size_t alignment) + ecs_entity_t id) { - 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_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]); } -ecs_entity_t ecs_component_init( +static +int32_t flecs_depth_for_flat_table( ecs_world_t *world, - const ecs_component_desc_t *desc) + ecs_table_t *table) { - 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_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)); +} - /* 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; - } +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_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); + 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); - bool new_component = true; - if (!result) { - result = ecs_new_low_id(world); - } else { - ecs_ensure(world, result); - new_component = ecs_has(world, result, EcsComponent); - } + 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); - 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)); + ecs_record_t **records = table->data.records.array; + ecs_entity_t *entities = table->data.entities.array; + for (i = 0; i < count; i ++) { + ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[i]->row); + if (flags & EcsEntityIsTarget) { + flecs_flatten(world, root, ecs_pair(rel, entities[i]), + depth + 1, desc); + } } - } - } else { - flecs_check_component(world, result, ptr, - desc->type.size, desc->type.alignment); - } - ecs_modified(world, result, EcsComponent); + 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; - 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); - } + dst = flecs_table_traverse_add(world, table, &root_pair, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); - if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { - world->info.last_component_id = result + 1; - } + dst = flecs_table_traverse_add(world, dst, &tgt_pair, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); - /* Ensure components cannot be deleted */ - ecs_add_pair(world, result, EcsOnDelete, EcsPanic); + 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); + } + } - 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); + if (!desc->keep_names) { + dst = flecs_table_traverse_remove(world, dst, &name_pair, &tmpdiff); + flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + } - return result; -error: - return 0; -} + int32_t dst_count = ecs_table_count(dst); -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); + 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); - const ecs_entity_t *ids; - if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { - return ids; - } + 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; - 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); + 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_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; + ecs_delete_with(world, pair); + flecs_id_record_release(world, idr); } -void ecs_clear( +void ecs_flatten( ecs_world_t *world, - ecs_entity_t entity) + ecs_id_t pair, + const ecs_flatten_desc_t *desc) { - 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_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_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *idr = flecs_id_record_get(world, pair); - ecs_table_t *table = r->table; - if (table) { - ecs_table_diff_t diff = { - .removed = table->type - }; + 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; + } - flecs_delete_entity(world, r, &diff); - r->table = NULL; + if (table->flags & EcsTableIsPrefab) { + continue; + } - if (r->row & EcsEntityIsTraversable) { - flecs_table_traversable_add(table, -1); + int32_t i, count = ecs_table_count(table); + ecs_record_t **records = table->data.records.array; + ecs_entity_t *entities = table->data.entities.array; + for (i = 0; i < count; i ++) { + ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[i]->row); + if (flags & EcsEntityIsTarget) { + flecs_flatten(world, root, ecs_pair(rel, entities[i]), 1, + &private_desc); + } + } } - } + } - flecs_defer_end(world, stage); -error: - return; + ecs_defer_end(world); } static -void flecs_throw_invalid_delete( - ecs_world_t *world, - ecs_id_t id) +const char* flecs_get_identifier( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) { - char *id_str = NULL; - if (!(world->flags & EcsWorldQuit)) { - id_str = ecs_id_str(world, id); - ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); + 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: - ecs_os_free(id_str); + return NULL; } -static -void flecs_marked_id_push( - ecs_world_t *world, - ecs_id_record_t* idr, - ecs_entity_t action, - bool delete_id) +const char* ecs_get_name( + const ecs_world_t *world, + ecs_entity_t entity) { - 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); + return flecs_get_identifier(world, entity, EcsName); } -static -void flecs_id_mark_for_delete( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action, - bool delete_id); +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 -void flecs_targets_mark_for_delete( +ecs_entity_t flecs_set_identifier( ecs_world_t *world, - ecs_table_t *table) + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t tag, + const char *name) { - ecs_id_record_t *idr; - ecs_entity_t *entities = ecs_vec_first(&table->data.entities); - ecs_record_t **records = ecs_vec_first(&table->data.records); - int32_t i, count = ecs_vec_count(&table->data.entities); - for (i = 0; i < count; i ++) { - ecs_record_t *r = records[i]; - if (!r) { - continue; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); - /* 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; - } + if (!entity) { + entity = ecs_new_id(world); + } - 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_OBJECT(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_OBJECT(idr->flags), true); - } - } + if (!name) { + ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); + return entity; } -} -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; + 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); } - return false; + + ecs_os_strset(&ptr->value, name); + ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); + + return entity; +error: + return 0; } -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 ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - ecs_entity_t result = action; - if (!result && delete_target) { - /* If action is not specified and we're deleting a relationship target, - * derive the action from the current record */ - ecs_table_record_t *trr = &table->_->records[tr->column]; - ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; - result = ECS_ID_ON_DELETE_OBJECT(idrr->flags); + if (!entity) { + return ecs_entity(world, { + .name = name + }); } - return result; + + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_set_identifier(world, stage, entity, EcsName, name); + + return entity; } -static -void flecs_update_monitors_for_delete( +ecs_entity_t ecs_set_symbol( ecs_world_t *world, - ecs_id_t id) + ecs_entity_t entity, + const char *name) { - flecs_update_component_monitors(world, NULL, &(ecs_type_t){ - .array = (ecs_id_t[]){id}, - .count = 1 - }); + return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); } -static -void flecs_id_mark_for_delete( +void ecs_set_alias( ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action, - bool delete_id) + ecs_entity_t entity, + const char *name) { - if (idr->flags & EcsIdMarkedForDelete) { - return; - } - - idr->flags |= EcsIdMarkedForDelete; - flecs_marked_id_push(world, idr, action, delete_id); - - ecs_id_t id = idr->id; + flecs_set_identifier(world, NULL, entity, EcsAlias, name); +} - 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); - } - } - } +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); } -static -bool flecs_on_delete_mark( - ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action, - bool delete_id) +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If there's no id record, there's nothing to delete */ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + /* 0 is not a valid entity id */ + if (!entity) { 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); - } + + /* Entity identifiers should not contain flag bits */ + if (entity & ECS_ID_FLAGS_MASK) { + return false; } - if (action == EcsPanic) { - /* This id is protected from deletion */ - flecs_throw_invalid_delete(world, id); + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { return false; } - flecs_id_mark_for_delete(world, idr, action, delete_id); + /* 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; + } - return true; + /* If id exists, it must be alive (the generation count must match) */ + return ecs_is_alive(world, entity); +error: + return false; } -static -void flecs_remove_from_table( - ecs_world_t *world, - ecs_table_t *table) +ecs_id_t ecs_strip_generation( + ecs_entity_t e) { - 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->column; - - 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); + /* If this is not a pair, erase the generation bits */ + if (!(e & ECS_ID_FLAGS_MASK)) { + e &= ~ECS_GENERATION_MASK; } - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + return e; +} - 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(); - } +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); - flecs_table_merge(world, dst_table, table, - &dst_table->data, &table->data); - } - } + world = ecs_get_world(world); - flecs_table_diff_builder_fini(world, &diff); + return flecs_entities_is_alive(world, entity); +error: + return false; } -static -bool flecs_on_delete_clear_tables( - ecs_world_t *world) +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t entity) { - /* 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; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return 0; + } - 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); - } - } - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - /* 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]); - } + if (flecs_entities_is_alive(world, entity)) { + return entity; + } - /* 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); - } + /* 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; + } - /* 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); + ecs_entity_t current = flecs_entities_get_generation(world, entity); + if (!current || !flecs_entities_is_alive(world, current)) { + return 0; + } - return true; + return current; +error: + return 0; } -static -bool flecs_on_delete_clear_ids( - ecs_world_t *world) +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t entity) { - 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; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - 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; - } - } + /* Const cast is safe, function checks for threading */ + world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); - ecs_id_record_t *idr = ids[i].idr; - bool delete_id = ids[i].delete_id; + /* 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); - flecs_id_record_release_tables(world, idr); + /* 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; + } - /* 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 the id is currently alive but did not match the argument, fail */ + ecs_check(!any, ECS_INVALID_PARAMETER, NULL); - /* 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); + /* 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); - return true; + /* Ensure id exists. The underlying datastructure will verify that the + * generation count matches the provided one. */ + flecs_entities_ensure(world, entity); +error: + return; } -static -void flecs_on_delete( +void ecs_ensure_id( ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action, - bool delete_id) + ecs_id_t 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); + 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); - /* Release remaining references to the ids */ - flecs_on_delete_clear_ids(world); + ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); - /* 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); + 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); } -#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(); + 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; } -void ecs_delete_with( - ecs_world_t *world, - ecs_id_t id) +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity) { - 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; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - flecs_on_delete(world, id, EcsDelete, false); - flecs_defer_end(world, stage); + world = ecs_get_world(world); - flecs_journal_end(); + return flecs_entities_exists(world, entity); +error: + return false; } -void ecs_remove_all( - ecs_world_t *world, - ecs_id_t id) +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity) { - flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); + 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_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { - return; - } + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + return record->table; +error: + return NULL; +} - flecs_on_delete(world, id, EcsRemove, false); - flecs_defer_end(world, stage); +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; + } - flecs_journal_end(); + return NULL; } -void ecs_delete( - ecs_world_t *world, - ecs_entity_t entity) +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(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); + world = ecs_get_world(world); - 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); + 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; } } - /* 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); + if (idr) { + return idr->type_info; + } else if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_type_info_get(world, id); + } error: - return; + return NULL; } -void ecs_add_id( - ecs_world_t *world, - ecs_entity_t entity, +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, 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); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + return ti->component; + } error: - return; + return 0; } -void ecs_remove_id( - ecs_world_t *world, - ecs_entity_t entity, +bool ecs_id_is_tag( + const ecs_world_t *world, 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; + 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; } -void ecs_override_id( - ecs_world_t *world, - ecs_entity_t entity, +bool ecs_id_is_union( + const ecs_world_t *world, 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; + 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; } -ecs_entity_t ecs_clone( - ecs_world_t *world, - ecs_entity_t dst, - ecs_entity_t src, - bool copy_value) +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_entity_t id) { 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; + if (!id) { + return 0; } - 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; - } + int32_t count = 0; + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = id, + .src.flags = EcsSelf, + .flags = EcsTermMatchDisabled|EcsTermMatchPrefab + }); - ecs_type_t src_type = src_table->type; - ecs_table_diff_t diff = { .added = src_type }; - ecs_record_t *dst_r = flecs_entities_get(world, dst); - flecs_new_entity(world, dst, dst_r, src_table, &diff, true, true); - int32_t row = ECS_RECORD_TO_ROW(dst_r->row); + it.flags |= EcsIterNoData; + it.flags |= EcsIterEvalTables; - if (copy_value) { - flecs_table_move(world, dst, src, src_table, - row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); - flecs_notify_on_set(world, src_table, row, 1, NULL, true); + while (ecs_term_next(&it)) { + count += it.count; } -done: - flecs_defer_end(world, stage); - return dst; + return count; error: return 0; } -const void* ecs_get_id( - const ecs_world_t *world, +void ecs_enable( + ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + bool enabled) { - 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 = NULL; - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - tr = flecs_id_record_get_table(idr, storage_table); + 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 the entity does not have a storage table (has no data) but it does - * have the id, the id must be a tag, and getting a tag is illegal. */ - ecs_check(!ecs_owns_id(world, entity, id), ECS_NOT_A_COMPONENT, NULL); - } - - if (!tr) { - return flecs_get_base_component(world, table, id, idr, 0); + if (enabled) { + ecs_remove_id(world, entity, EcsDisabled); + } else { + ecs_add_id(world, entity, EcsDisabled); + } } +} - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_get_component_w_index(table, tr->column, row).ptr; +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 NULL; + return false; } -void* ecs_get_mut_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +bool ecs_defer_end( + ecs_world_t *world) { 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, EcsOpMut, entity, id, 0, NULL, true); - } - - 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; + return flecs_defer_end(world, stage); error: - return NULL; + return false; } -static -ecs_record_t* flecs_access_begin( - ecs_world_t *stage, - ecs_entity_t entity, - bool write) +void ecs_defer_suspend( + ecs_world_t *world) { - 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; + 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 NULL; + return; } -static -void flecs_access_end( - const ecs_record_t *r, - bool write) +void ecs_defer_resume( + ecs_world_t *world) { - 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); - + 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; } -ecs_record_t* ecs_write_begin( - ecs_world_t *world, +const char* ecs_id_flag_str( ecs_entity_t entity) { - return flecs_access_begin(world, entity, true); + 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_write_end( - ecs_record_t *r) +void ecs_id_str_buf( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) { - flecs_access_end(r, true); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); -const ecs_record_t* ecs_read_begin( - ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_access_begin(world, entity, false); -} + world = ecs_get_world(world); -void ecs_read_end( - const ecs_record_t *r) -{ - flecs_access_end(r, false); -} + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); + ecs_strbuf_appendch(buf, '|'); + } -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; + if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); + ecs_strbuf_appendch(buf, '|'); } - return ecs_vec_get_t(&table->data.entities, ecs_entity_t, - ECS_RECORD_TO_ROW(record->row))[0]; -error: - return 0; -} + if (ECS_HAS_ID_FLAG(id, AND)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); + ecs_strbuf_appendch(buf, '|'); + } -const void* ecs_record_get_id( - 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); -} + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t obj = ECS_PAIR_SECOND(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); + 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); } - return false; + +error: + return; } -void* ecs_record_get_mut_id( - ecs_world_t *stage, - ecs_record_t *r, +char* ecs_id_str( + const ecs_world_t *world, 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_strbuf_t buf = ECS_STRBUF_INIT; + ecs_id_str_buf(world, id, &buf); + return ecs_strbuf_get(&buf); } -ecs_ref_t ecs_ref_init_id( +static +void ecs_type_str_buf( const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) + const ecs_type_t *type, + ecs_strbuf_t *buf) { - 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_entity_t *ids = type->array; + int32_t i, count = type->count; - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_check(record != NULL, ECS_INVALID_PARAMETER, - "cannot create ref for empty entity"); + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; - ecs_ref_t result = { - .entity = entity, - .id = id, - .record = record - }; + if (i) { + ecs_strbuf_appendch(buf, ','); + ecs_strbuf_appendch(buf, ' '); + } - ecs_table_t *table = record->table; - if (table) { - result.tr = flecs_table_record_get(world, table, id); + if (id == 1) { + ecs_strbuf_appendlit(buf, "Component"); + } else { + ecs_id_str_buf(world, id, buf); + } } - - return result; -error: - return (ecs_ref_t){0}; } -void ecs_ref_update( +char* ecs_type_str( const ecs_world_t *world, - ecs_ref_t *ref) + const ecs_type_t *type) { - 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; + if (!type) { + return ecs_os_strdup(""); } - ecs_table_record_t *tr = ref->tr; - if (!tr || tr->hdr.table != 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); - } -error: - return; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_type_str_buf(world, type, &buf); + return ecs_strbuf_get(&buf); } -void* ecs_ref_get_id( +char* ecs_table_str( const ecs_world_t *world, - ecs_ref_t *ref, - ecs_id_t id) + const ecs_table_t *table) { - 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) { + if (table) { + return ecs_type_str(world, &table->type); + } else { 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 (!tr || tr->hdr.table != table) { - tr = ref->tr = flecs_table_record_get(world, table, id); - if (!tr) { - 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_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, 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, ']'); - int32_t column = ecs_table_type_to_storage_index(table, tr->column); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); - return flecs_get_component_w_index(table, column, row).ptr; + return ecs_strbuf_get(&buf); error: return NULL; } -void* ecs_emplace_id( +static +void flecs_flush_bulk_new( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) + ecs_cmd_t *cmd) { - 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); + ecs_entity_t *entities = cmd->is._n.entities; - if (flecs_defer_cmd(stage)) { - return flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, - true); + 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_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; + ecs_os_free(entities); } static -void flecs_modified_id_if( +void flecs_dtor_value( 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; - } - - 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_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); + } } - - 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; } -void ecs_modified_id( +static +void flecs_discard_cmd( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) + ecs_cmd_t *cmd) { - 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 (cmd->kind != EcsOpBulkNew) { + void *value = cmd->is._1.value; + if (value) { + flecs_dtor_value(world, cmd->id, value, 1); + flecs_stack_free(value, cmd->is._1.size); + } + } else { + ecs_os_free(cmd->is._n.entities); } - - /* 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( +bool flecs_remove_invalid( ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, ecs_id_t id, - size_t size, - void *ptr) + ecs_id_t *id_out) { - if (flecs_defer_cmd(stage)) { - flecs_defer_set(world, stage, EcsOpSet, entity, id, - flecs_utosize(size), ptr, false); - 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); + 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_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); + 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 { - 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); + 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; + } } - flecs_defer_end(world, stage); -error: - return; + return true; } static -void flecs_move_ptr_w_id( +void flecs_cmd_batch_for_entity( ecs_world_t *world, - ecs_stage_t *stage, + ecs_table_diff_builder_t *diff, ecs_entity_t entity, - ecs_id_t id, - size_t size, - void *ptr, - ecs_cmd_kind_t cmd_kind) + ecs_cmd_t *cmds, + int32_t start) { - if (flecs_defer_cmd(stage)) { - flecs_defer_set(world, stage, cmd_kind, entity, id, - flecs_utosize(size), ptr, false); - 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 != EcsOpEmplace) { - /* 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 == EcsOpSet) { - 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); - } + ecs_table_t *table = NULL; + if (r) { + table = r->table; } - flecs_defer_end(world, stage); -error: - return; -} + world->info.cmd.batched_entity_count ++; -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_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; - ecs_stage_t *stage = flecs_stage_from_world(&world); + 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; + } - if (!entity) { - entity = ecs_new_id(world); - ecs_entity_t scope = stage->scope; - if (scope) { - ecs_add_pair(world, entity, EcsChildOf, scope); + /* 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 = EcsOpSkip; + continue; + } + /* Entity should remain alive and id is still valid */ + } else { + /* Id was no longer valid and had a Delete policy */ + cmd->kind = EcsOpSkip; + ecs_delete(world, entity); + flecs_table_diff_builder_clear(diff); + return; + } } - } - /* Safe to cast away const: function won't modify if move arg is false */ - flecs_copy_ptr_w_id(world, stage, entity, id, size, (void*)ptr); - return entity; -error: - return 0; -} + ecs_cmd_kind_t kind = cmd->kind; + switch(kind) { + case EcsOpAddModified: + /* Add is batched, but keep Modified */ + cmd->kind = EcsOpModified; -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); + /* fall through */ + case EcsOpAdd: + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + break; + case EcsOpModified: + 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)) { + flecs_invoke_hook(world, start_table, 1, row, &entity, + ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); + } + } + } + break; + case EcsOpSet: + case EcsOpMut: + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + has_set = true; + break; + case EcsOpEmplace: + /* Don't add for emplace, as this requires a special call to ensure + * the constructor is not invoked for the component */ + break; + case EcsOpRemove: + table = flecs_find_table_remove(world, table, id, diff); + world->info.cmd.batched_command_count ++; + break; + case EcsOpClear: + 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 EcsOpClone: + case EcsOpBulkNew: + case EcsOpPath: + case EcsOpDelete: + case EcsOpOnDeleteAction: + case EcsOpEnable: + case EcsOpDisable: + case EcsOpSkip: + break; + } - ecs_stage_t *stage = flecs_stage_from_world(&world); + /* Add, remove and clear operations can be skipped since they have no + * side effects besides adding/removing components */ + if (kind == EcsOpAdd || kind == EcsOpRemove || kind == EcsOpClear) { + cmd->kind = EcsOpSkip; + } + } while ((cur = next_for_entity)); - if (flecs_defer_enable(stage, entity, id, enable)) { - return; - } else { - /* Operations invoked by enable/disable should not be deferred */ - stage->defer --; - } + /* 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); - 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_search(world, table, bs_id, 0); - } + /* If ids were both removed and set, check if there are ids that were both + * set and removed. If so, skip the set command so that the id won't get + * re-added */ + if (has_set && table_diff.removed.count) { + 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 EcsOpSet: + case EcsOpMut: { + ecs_id_record_t *idr = cmd->idr; + if (!idr) { + idr = flecs_id_record_get(world, cmd->id); + } - if (index == -1) { - ecs_add_id(world, entity, bs_id); - ecs_enable_id(world, entity, id, enable); - return; + if (!flecs_id_record_get_table(idr, table)) { + /* Component was deleted */ + cmd->kind = EcsOpSkip; + world->info.cmd.batched_command_count --; + world->info.cmd.discard_count ++; + } + break; + } + case EcsOpClone: + case EcsOpBulkNew: + case EcsOpAdd: + case EcsOpRemove: + case EcsOpEmplace: + case EcsOpModified: + case EcsOpAddModified: + case EcsOpPath: + case EcsOpDelete: + case EcsOpClear: + case EcsOpOnDeleteAction: + case EcsOpEnable: + case EcsOpDisable: + case EcsOpSkip: + break; + } + } while ((cur = next_for_entity)); } - - 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) +/* Leave safe section. Run all deferred commands. */ +bool flecs_defer_end( + ecs_world_t *world, + ecs_stage_t *stage) { - 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_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); - 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) { + 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_entity_t bs_id = id | ECS_TOGGLE; - int32_t index = ecs_search(world, table, bs_id, 0); - 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(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); - 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]; + 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; + } - return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); -error: - return false; -} + ecs_stage_t *dst_stage = flecs_stage_from_world(&world); + if (ecs_vec_count(&stage->commands)) { + ecs_vec_t commands = stage->commands; + stage->commands.array = NULL; + stage->commands.count = 0; + stage->commands.size = 0; -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); + ecs_cmd_t *cmds = ecs_vec_first(&commands); + int32_t i, count = ecs_vec_count(&commands); + ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + ecs_stack_t stack = stage->defer_stack; + flecs_stack_init(&stage->defer_stack); - 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_table_diff_builder_t diff; + flecs_table_diff_builder_init(world, &diff); + flecs_sparse_clear(&stage->cmd_entries); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - int32_t column; - if (idr) { - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (tr) { - return true; - } - } + 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); - if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { - return false; - } + /* 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 ++; + } + } - ecs_table_record_t *tr; - column = ecs_search_relation(world, table, 0, id, - EcsIsA, 0, 0, 0, &tr); - if (column == -1) { - return false; - } + /* 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 != EcsOpPath) && ((kind == EcsOpSkip) || (e && !is_alive))) { + world->info.cmd.discard_count ++; + flecs_discard_cmd(world, cmd); + continue; + } - 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(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_id_t id = cmd->id; - 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; - } + switch(kind) { + case EcsOpAdd: + 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 EcsOpRemove: + flecs_remove_id(world, e, id); + world->info.cmd.remove_count ++; + break; + case EcsOpClone: + ecs_clone(world, e, id, cmd->is._1.clone_value); + break; + case EcsOpSet: + 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 EcsOpEmplace: + 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 EcsOpMut: + 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 EcsOpModified: + flecs_modified_id_if(world, e, id); + world->info.cmd.modified_count ++; + break; + case EcsOpAddModified: + flecs_add_id(world, e, id); + flecs_modified_id_if(world, e, id); + world->info.cmd.add_count ++; + world->info.cmd.modified_count ++; + break; + case EcsOpDelete: { + ecs_delete(world, e); + world->info.cmd.delete_count ++; + break; + } + case EcsOpClear: + ecs_clear(world, e); + world->info.cmd.clear_count ++; + break; + case EcsOpOnDeleteAction: + ecs_defer_begin(world); + flecs_on_delete(world, id, e, false); + ecs_defer_end(world); + world->info.cmd.other_count ++; + break; + case EcsOpEnable: + ecs_enable_id(world, e, id, true); + world->info.cmd.other_count ++; + break; + case EcsOpDisable: + ecs_enable_id(world, e, id, false); + world->info.cmd.other_count ++; + break; + case EcsOpBulkNew: + flecs_flush_bulk_new(world, cmd); + world->info.cmd.other_count ++; + continue; + case EcsOpPath: + 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 EcsOpSkip: + break; + } - 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->column - table->_->sw_offset]; - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_switch_get(sw, row); - + if (cmd->is._1.value) { + flecs_stack_free(cmd->is._1.value, cmd->is._1.size); + } } - } - 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); - } - } + ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); - if (index >= tr->count) { - index -= tr->count; - goto look_in_base; - } + /* Restore defer queue */ + ecs_vec_clear(&commands); + stage->commands = commands; - return ecs_pair_second(world, table->type.array[tr->column + index]); -look_in_base: - if (table->flags & EcsTableHasIsA) { - const ecs_table_record_t *isa_tr = flecs_id_record_get_table( - world->idr_isa_wildcard, table); - ecs_assert(isa_tr != NULL, ECS_INTERNAL_ERROR, NULL); + /* Restore stack */ + flecs_stack_fini(&stage->defer_stack); + stage->defer_stack = stack; + flecs_stack_reset(&stage->defer_stack); - ecs_id_t *ids = table->type.array; - int32_t i = isa_tr->column, end = (i + isa_tr->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; - } + flecs_table_diff_builder_fini(world, &diff); } - } -not_found: -error: - return 0; -} + return true; + } -ecs_entity_t ecs_get_parent( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return ecs_get_target(world, entity, EcsChildOf, 0); + return false; } -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) +/* 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(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 */ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - if (table) { - ecs_id_t *ids = table->type.array; - int32_t i, count = table->type.count; + if (!--stage->defer) { + ecs_vec_t commands = stage->commands; + 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 ++) { - 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; - } + flecs_discard_cmd(world, &cmds[i]); } - } - } - if (subject == 0) { - return entity; - } else { - return subject; - } -error: - return 0; -} + ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); -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_vec_clear(&commands); + flecs_stack_reset(&stage->defer_stack); + flecs_sparse_clear(&stage->cmd_entries); + } - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return ecs_table_get_depth(world, table, rel); + return true; } - return 0; error: - return -1; + return false; } -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]; -} +/** + * @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 -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) +int flecs_entity_filter_find_smallest_term( + ecs_table_t *table, + ecs_entity_filter_iter_t *iter) { - 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)); -} + 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; -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; - } + for (i = 0; i < count; i ++) { + /* The array with sparse queries for the matched table */ + flecs_switch_term_t *sparse_column = &sw_terms[i]; - 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); + /* Pointer to the switch column struct of the table */ + ecs_switch_t *sw = sparse_column->sw_column; - 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); + /* 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]; - ecs_record_t **records = table->data.records.array; - ecs_entity_t *entities = table->data.entities.array; - for (i = 0; i < count; i ++) { - ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[i]->row); - if (flags & EcsEntityIsTarget) { - flecs_flatten(world, root, ecs_pair(rel, entities[i]), - depth + 1, desc); - } - } + /* 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); - 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; + /* Get the sparse column */ + sw = sparse_column->sw_column = + &table->_->sw_columns[table_column_index - 1]; + } - dst = flecs_table_traverse_add(world, table, &root_pair, &tmpdiff); - flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + /* 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; + } + } - dst = flecs_table_traverse_add(world, dst, &tgt_pair, &tmpdiff); - flecs_table_diff_build_append_table(world, &diff, &tmpdiff); + return index; +} - 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); - } - } +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 (!desc->keep_names) { - dst = flecs_table_traverse_remove(world, dst, &name_pair, &tmpdiff); - flecs_table_diff_build_append_table(world, &diff, &tmpdiff); - } + if (!(switch_smallest = iter->sw_smallest)) { + switch_smallest = iter->sw_smallest = + flecs_entity_filter_find_smallest_term(table, iter); + first_iteration = true; + } - int32_t dst_count = ecs_table_count(dst); + switch_smallest -= 1; - 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); + 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; - 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; + /* Find next entity to iterate in sparse column */ + int32_t first, sparse_first = iter->sw_offset; - 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); + 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; } } } - 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; + if (first == -1) { + goto done; } - - 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) { + /* 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; } - if (table->flags & EcsTableIsPrefab) { - continue; - } + column = &columns[i]; + sw = column->sw_column; - int32_t i, count = ecs_table_count(table); - ecs_record_t **records = table->data.records.array; - ecs_entity_t *entities = table->data.entities.array; - for (i = 0; i < count; i ++) { - ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[i]->row); - if (flags & EcsEntityIsTarget) { - flecs_flatten(world, root, ecs_pair(rel, entities[i]), 1, - &private_desc); + if (flecs_switch_get(sw, first) != column->sw_case) { + first = flecs_switch_next(sw_smallest, first); + if (first == -1) { + goto done; } } } - } - - 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); + } while (i != count); - const EcsIdentifier *ptr = ecs_get_pair( - world, entity, EcsIdentifier, tag); + iter->range.offset = iter->sw_offset = first; + iter->range.count = 1; - if (ptr) { - return ptr->value; - } else { - return NULL; - } -error: - return NULL; -} + 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; -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_get_identifier(world, entity, EcsName); + return -1; } -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; - } -} +#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) 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) +int flecs_entity_filter_bitset_next( + ecs_table_t *table, + ecs_entity_filter_iter_t *iter) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); + /* 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 + }; - if (!entity) { - entity = ecs_new_id(world); - } + /* 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) + }; - if (!name) { - ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); - return entity; - } + 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; - EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < count; i ++) { + flecs_bitset_term_t *column = &terms[i]; + ecs_bitset_t *bs = terms[i].bs_column; - 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); - } + 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; - ecs_os_strset(&ptr->value, name); - ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); - - return entity; -error: - return 0; -} + if (bs_block >= bs_block_count) { + goto done; + } -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 - }); - } + uint64_t *data = bs->data; + int32_t bs_start = first & 0x3F; - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_set_identifier(world, stage, entity, EcsName, name); + /* 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; + } - return entity; -} + bs_start = 0; + remain = BS_MAX; /* Test the full block */ + v = data[bs_block]; + } -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); -} + /* Step 2: find the first non-empty element in the block */ + while (!(v & bitmask[bs_start])) { + bs_start ++; -void ecs_set_alias( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - flecs_set_identifier(world, NULL, entity, EcsAlias, name); -} + /* Block was not empty, so bs_start must be smaller than 64 */ + ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); + } -ecs_id_t ecs_make_pair( - ecs_entity_t relationship, - ecs_entity_t target) -{ - return ecs_pair(relationship, target); -} + /* Step 3: Find number of contiguous enabled elements after start */ + int32_t bs_end = bs_start, bs_block_end = bs_block; -bool ecs_is_valid( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + remain = bitmask_remain[bs_end]; + while ((v & remain) == remain) { + bs_end = 0; + bs_block_end ++; - /* 0 is not a valid entity id */ - if (!entity) { - return false; - } - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - /* Entity identifiers should not contain flag bits */ - if (entity & ECS_ID_FLAGS_MASK) { - return false; - } + if (bs_block_end == bs_block_count) { + break; + } - /* Entities should not contain data in dead zone bits */ - if (entity & ~0xFF00FFFFFFFFFFFF) { - return false; - } + v = data[bs_block_end]; + remain = BS_MAX; /* Test the full block */ + } - if (entity & ECS_ID_FLAG_BIT) { - return ecs_entity_t_lo(entity) != 0; - } + /* Step 4: find remainder of enabled elements in current block */ + if (bs_block_end != bs_block_count) { + while ((v & bitmask[bs_end])) { + bs_end ++; + } + } - /* 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; - } + /* Block was not 100% occupied, so bs_start must be smaller than 64 */ + ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); - /* If id exists, it must be alive (the generation count must match) */ - return ecs_is_alive(world, entity); -error: - return false; -} + /* 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; + } -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; - } + /* 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; + } - return e; -} + /* 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; + } + } -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); + last = cur_last; + int32_t elem_count = last - first; - world = ecs_get_world(world); + /* 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; + } - return flecs_entities_is_alive(world, entity); -error: - return false; + /* 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; } -ecs_entity_t ecs_get_alive( - const ecs_world_t *world, - ecs_entity_t entity) +#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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!entity) { - return 0; + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return -1; } - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + ecs_record_t *r = cur->target; + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - if (flecs_entities_is_alive(world, entity)) { - return entity; + ecs_table_t *table = r->table; + if (!table) { + return -1; } - /* 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_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; } - ecs_entity_t current = flecs_entities_get_generation(world, entity); - if (!current || !flecs_entities_is_alive(world, current)) { - return 0; + 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 current; -error: - return 0; + return ecs_search_relation( + world, table, 0, id, rel, EcsSelf|EcsUp, src_out, NULL, tr_out); } -void ecs_ensure( +void flecs_entity_filter_init( ecs_world_t *world, - ecs_entity_t entity) + ecs_entity_filter_t **entity_filter, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_id_t *ids, + int32_t *columns) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + 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; - /* Const cast is safe, function checks for threading */ - world = (ecs_world_t*)ecs_get_world(world); + /* Look for union fields */ + if (table->flags & EcsTableHasUnion) { + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } - /* 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); + 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; + } - /* 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; + 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; + } } - /* If the id is currently alive but did not match the argument, fail */ - ecs_check(!any, ECS_INVALID_PARAMETER, NULL); + /* Look for disabled fields */ + if (table->flags & EcsTableHasToggle) { + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } - /* 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); + 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); - /* Ensure id exists. The underlying datastructure will verify that the - * generation count matches the provided one. */ - flecs_entities_ensure(world, entity); -error: - return; -} + 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; + } + } + } -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); + /* 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]); - ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); + for (i = 0; i < term_count; i ++) { + if (ecs_term_match_0(&terms[i])) { + continue; + } - 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); + 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); + } } - } 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; + 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; + } } -ecs_table_t* ecs_get_table( - const ecs_world_t *world, - ecs_entity_t entity) +void flecs_entity_filter_fini( + ecs_world_t *world, + ecs_entity_filter_t *ef) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); + if (!ef) { + return; + } - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - return record->table; -error: - return NULL; -} + ecs_allocator_t *a = &world->allocator; -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; + 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); } - return NULL; + 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); } -const ecs_type_info_t* ecs_get_type_info( - const ecs_world_t *world, - ecs_id_t id) +int flecs_entity_filter_next( + ecs_entity_filter_iter_t *it) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + 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; - world = ecs_get_world(world); + do { + found = false; - 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 (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 (!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 (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 (idr) { - return idr->type_info; - } else if (!(id & ECS_ID_FLAGS_MASK)) { - return flecs_type_info_get(world, id); - } -error: - return NULL; -} + 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; -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) { - return ti->component; - } -error: - return 0; -} + 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 ++; + } -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; - } + 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 { - /* 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; + 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 { - /* If relationship is wildcard id is not guaranteed to be a tag */ + 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 { - if (ecs_get_typeid(world, id) == 0) { - return true; - } + return result; } - - return false; } -bool ecs_id_is_union( - const ecs_world_t *world, - ecs_id_t id) +/** + * @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) { - 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; + 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; + } } - return false; + if (name) { + ecs_strbuf_appendstrn(buf, name, name_len); + } else { + ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); + } + + return cur != 0; } -int32_t ecs_count_id( - const ecs_world_t *world, - ecs_entity_t id) +bool flecs_name_is_id( + const char *name) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!id) { - return 0; + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!isdigit(name[0])) { + return false; } - int32_t count = 0; - ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { - .id = id, - .src.flags = EcsSelf - }); - - it.flags |= EcsIterNoData; - it.flags |= EcsIterEvalTables; + ecs_size_t i, length = ecs_os_strlen(name); + for (i = 1; i < length; i ++) { + char ch = name[i]; - while (ecs_term_next(&it)) { - count += it.count; + if (!isdigit(ch)) { + break; + } } - return count; -error: - return 0; + return i >= length; } -void ecs_enable( - ecs_world_t *world, - ecs_entity_t entity, - bool enabled) +ecs_entity_t flecs_name_to_id( + const ecs_world_t *world, + const char *name) { - 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); - } + 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 (enabled) { - ecs_remove_id(world, entity, EcsDisabled); + if ((uint32_t)result == (uint64_t)result) { + return (ecs_entity_t)result; } else { - ecs_add_id(world, entity, EcsDisabled); + return 0; } } } -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) +static +ecs_entity_t flecs_get_builtin( + const char *name) { - 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; -} + 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; + } -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; + return 0; } -void ecs_defer_resume( - ecs_world_t *world) +static +bool flecs_is_sep( + const char **ptr, + const char *sep) { - 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; -} + ecs_size_t len = ecs_os_strlen(sep); -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"; + if (!ecs_os_strncmp(*ptr, sep, len)) { + *ptr += len; + return true; } else { - return "UNKNOWN"; + return false; } } -void ecs_id_str_buf( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) +static +const char* flecs_path_elem( + const char *path, + const char *sep, + int32_t *len) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t count = 0; - world = ecs_get_world(world); + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; + } - if (ECS_HAS_ID_FLAG(id, TOGGLE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); - ecs_strbuf_appendch(buf, '|'); + ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); + + if (!template_nesting && flecs_is_sep(&ptr, sep)) { + break; + } + + count ++; } - if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); - ecs_strbuf_appendch(buf, '|'); + if (len) { + *len = count; } - if (ECS_HAS_ID_FLAG(id, AND)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); - ecs_strbuf_appendch(buf, '|'); + if (count) { + return ptr; + } else { + return NULL; } +error: + return NULL; +} - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); +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; + } +} - ecs_entity_t e; - if ((e = ecs_get_alive(world, rel))) { - rel = e; - } - if ((e = ecs_get_alive(world, obj))) { - obj = e; - } +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; - 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, ')'); + 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_entity_t e = id & ECS_COMPONENT_MASK; - ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); + ecs_strbuf_appendstrn(buf, "", 0); } error: return; } -char* ecs_id_str( +char* ecs_get_path_w_sep( const ecs_world_t *world, - ecs_id_t id) + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix) { ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_id_str_buf(world, id, &buf); + ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); return ecs_strbuf_get(&buf); } -static -void ecs_type_str_buf( +ecs_entity_t ecs_lookup_child( const ecs_world_t *world, - const ecs_type_t *type, - ecs_strbuf_t *buf) + ecs_entity_t parent, + const char *name) { - ecs_entity_t *ids = type->array; - int32_t i, count = type->count; - - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - if (i) { - ecs_strbuf_appendch(buf, ','); - ecs_strbuf_appendch(buf, ' '); + 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; } + } - if (id == 1) { - ecs_strbuf_appendlit(buf, "Component"); - } else { - ecs_id_str_buf(world, id, buf); - } + 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; } -char* ecs_type_str( +ecs_entity_t ecs_lookup( const ecs_world_t *world, - const ecs_type_t *type) -{ - if (!type) { - return ecs_os_strdup(""); + const char *name) +{ + if (!name) { + return 0; } - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_type_str_buf(world, type, &buf); - return ecs_strbuf_get(&buf); + 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; } -char* ecs_table_str( +ecs_entity_t ecs_lookup_symbol( const ecs_world_t *world, - const ecs_table_t *table) -{ - if (table) { - return ecs_type_str(world, &table->type); - } else { - return NULL; + 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; } -char* ecs_entity_str( +ecs_entity_t ecs_lookup_path_w_sep( const ecs_world_t *world, - ecs_entity_t entity) + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix, + bool recursive) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + if (!path) { + return 0; + } - 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_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; } - ecs_strbuf_appendch(&buf, ']'); - return ecs_strbuf_get(&buf); + 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 NULL; + return 0; } -static -void flecs_flush_bulk_new( +ecs_entity_t ecs_set_scope( ecs_world_t *world, - ecs_cmd_t *cmd) + ecs_entity_t scope) { - ecs_entity_t *entities = cmd->is._n.entities; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - 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_entity_t cur = stage->scope; + stage->scope = scope; - ecs_os_free(entities); + return cur; +error: + return 0; } -static -void flecs_dtor_value( +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, - ecs_id_t id, - void *value, - int32_t count) + const ecs_entity_t *lookup_path) { - 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); - } - } + 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; } -static -void flecs_discard_cmd( +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, - ecs_cmd_t *cmd) + const char *prefix) { - if (cmd->kind != EcsOpBulkNew) { - void *value = cmd->is._1.value; - if (value) { - flecs_dtor_value(world, cmd->id, value, 1); - flecs_stack_free(value, cmd->is._1.size); - } - } else { - ecs_os_free(cmd->is._n.entities); - } + ecs_poly_assert(world, ecs_world_t); + const char *old_prefix = world->info.name_prefix; + world->info.name_prefix = prefix; + return old_prefix; } static -bool flecs_remove_invalid( +void flecs_add_path( ecs_world_t *world, - ecs_id_t id, - ecs_id_t *id_out) + bool defer_suspend, + ecs_entity_t parent, + ecs_entity_t entity, + const char *name) { - 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_OBJECT(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; - } + 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); } - return true; + 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); + } } -static -void flecs_cmd_batch_for_entity( +ecs_entity_t ecs_add_path_w_sep( ecs_world_t *world, - ecs_table_diff_builder_t *diff, ecs_entity_t entity, - ecs_cmd_t *cmds, - int32_t start) + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) { - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = NULL; - if (r) { - table = r->table; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world->info.cmd.batched_entity_count ++; + if (!sep) { + sep = "."; + } - 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; + if (!path) { + if (!entity) { + entity = ecs_new_id(world); + } - 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; + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); } - /* 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 = EcsOpSkip; - continue; - } - /* Entity should remain alive and id is still valid */ - } else { - /* Id was no longer valid and had a Delete policy */ - cmd->kind = EcsOpSkip; - ecs_delete(world, entity); - flecs_table_diff_builder_clear(diff); - return; - } - } - - ecs_cmd_kind_t kind = cmd->kind; - switch(kind) { - case EcsOpAddModified: - /* Add is batched, but keep Modified */ - cmd->kind = EcsOpModified; - - /* fallthrough */ - case EcsOpAdd: - table = flecs_find_table_add(world, table, id, diff); - world->info.cmd.batched_command_count ++; - break; - case EcsOpModified: { - 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) { - ecs_type_info_t *ti = ptr.ti; - ecs_iter_action_t on_set; - if ((on_set = ti->hooks.on_set)) { - flecs_invoke_hook(world, start_table, 1, row, &entity, - ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); - } - } - } - break; - } - case EcsOpSet: - case EcsOpMut: - table = flecs_find_table_add(world, table, id, diff); - world->info.cmd.batched_command_count ++; - has_set = true; - break; - case EcsOpEmplace: - /* Don't add for emplace, as this requires a special call to ensure - * the constructor is not invoked for the component */ - break; - case EcsOpRemove: - table = flecs_find_table_remove(world, table, id, diff); - world->info.cmd.batched_command_count ++; - break; - case EcsOpClear: - 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; - default: - break; - } + return entity; + } - /* Add, remove and clear operations can be skipped since they have no - * side effects besides adding/removing components */ - if (kind == EcsOpAdd || kind == EcsOpRemove || kind == EcsOpClear) { - cmd->kind = EcsOpSkip; - } - } while ((cur = next_for_entity)); + bool root_path = flecs_is_root_path(path, prefix); + parent = flecs_get_parent_from_path(world, parent, &path, prefix, !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); + 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 ids were both removed and set, check if there are ids that were both - * set and removed. If so, skip the set command so that the id won't get - * re-added */ - if (has_set && table_diff.removed.count) { - 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 EcsOpSet: - case EcsOpMut: { - ecs_id_record_t *idr = cmd->idr; - if (!idr) { - idr = flecs_id_record_get(world, cmd->id); + 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; } - if (!flecs_id_record_get_table(idr, table)) { - /* Component was deleted */ - cmd->kind = EcsOpSkip; - world->info.cmd.batched_command_count --; - world->info.cmd.discard_count ++; - } - break; - } - default: - break; + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; } - } 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); - if (ecs_vec_count(&stage->commands)) { - ecs_vec_t commands = stage->commands; - stage->commands.array = NULL; - stage->commands.count = 0; - stage->commands.size = 0; - - ecs_cmd_t *cmds = ecs_vec_first(&commands); - int32_t i, count = ecs_vec_count(&commands); - ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); - - ecs_stack_t stack = stage->defer_stack; - flecs_stack_init(&stage->defer_stack); - - ecs_table_diff_builder_t diff; - flecs_table_diff_builder_init(world, &diff); - flecs_sparse_clear(&stage->cmd_entries); - for (i = 0; i < count; i ++) { - ecs_cmd_t *cmd = &cmds[i]; - ecs_entity_t e = cmd->entity; - bool is_alive = flecs_entities_is_valid(world, e); + elem[len] = '\0'; + ptr_start = ptr; - /* 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 ++; - } + ecs_entity_t e = ecs_lookup_child(world, cur, elem); + if (!e) { + if (name) { + ecs_os_free(name); } - /* 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 != EcsOpPath) && ((kind == EcsOpSkip) || (e && !is_alive))) { - world->info.cmd.discard_count ++; - flecs_discard_cmd(world, cmd); - continue; - } + name = ecs_os_strdup(elem); - ecs_id_t id = cmd->id; + /* 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; + } - switch(kind) { - case EcsOpAdd: - 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 ++; - } + 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 { - world->info.cmd.discard_count ++; - ecs_delete(world, e); - } - break; - case EcsOpRemove: - flecs_remove_id(world, e, id); - world->info.cmd.remove_count ++; - break; - case EcsOpClone: - ecs_clone(world, e, id, cmd->is._1.clone_value); - break; - case EcsOpSet: - 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 EcsOpEmplace: - 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 EcsOpMut: - 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 EcsOpModified: - flecs_modified_id_if(world, e, id); - world->info.cmd.modified_count ++; - break; - case EcsOpAddModified: - flecs_add_id(world, e, id); - flecs_modified_id_if(world, e, id); - world->info.cmd.add_count ++; - world->info.cmd.modified_count ++; - break; - case EcsOpDelete: { - ecs_delete(world, e); - world->info.cmd.delete_count ++; - break; - } - case EcsOpClear: - ecs_clear(world, e); - world->info.cmd.clear_count ++; - break; - case EcsOpOnDeleteAction: - ecs_defer_begin(world); - flecs_on_delete(world, id, e, false); - ecs_defer_end(world); - world->info.cmd.other_count ++; - break; - case EcsOpEnable: - ecs_enable_id(world, e, id, true); - world->info.cmd.other_count ++; - break; - case EcsOpDisable: - ecs_enable_id(world, e, id, false); - world->info.cmd.other_count ++; - break; - case EcsOpBulkNew: - flecs_flush_bulk_new(world, cmd); - world->info.cmd.other_count ++; - continue; - case EcsOpPath: - ecs_ensure(world, e); - if (cmd->id) { - ecs_add_pair(world, e, EcsChildOf, cmd->id); + e = ecs_new_id(world); } - ecs_set_name(world, e, cmd->is._1.value); - ecs_os_free(cmd->is._1.value); - cmd->is._1.value = NULL; - break; - case EcsOpSkip: - break; } - if (cmd->is._1.value) { - flecs_stack_free(cmd->is._1.value, cmd->is._1.size); + if (!cur && last_elem && root_path) { + ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); } - } - ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); + flecs_add_path(world, suspend_defer, cur, e, name); + } - /* Restore defer queue */ - ecs_vec_clear(&commands); - stage->commands = commands; + cur = e; + } - /* Restore stack */ - flecs_stack_fini(&stage->defer_stack); - stage->defer_stack = stack; - flecs_stack_reset(&stage->defer_stack); + if (entity && (cur != entity)) { + ecs_throw(ECS_ALREADY_DEFINED, name); + } - flecs_table_diff_builder_fini(world, &diff); + if (name) { + ecs_os_free(name); } - return true; + if (elem != buff) { + ecs_os_free(elem); + } + } else { + flecs_add_path(world, suspend_defer, parent, entity, path); } - return false; + return cur; +error: + return 0; } -/* Delete operations from queue without executing them. */ -bool flecs_defer_purge( +ecs_entity_t ecs_new_from_path_w_sep( ecs_world_t *world, - ecs_stage_t *stage) + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!--stage->defer) { - ecs_vec_t commands = stage->commands; - - 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->commands, ecs_cmd_t); - - ecs_vec_clear(&commands); - flecs_stack_reset(&stage->defer_stack); - flecs_sparse_clear(&stage->cmd_entries); - } - - return true; - } - -error: - return false; + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } /** - * @file stage.c - * @brief Staging implementation. + * @file filter.c + * @brief Uncached query 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. + * 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. * - * 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. + * 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. * - * In a multi threaded application, each thread has its own stage which allows - * threads to insert mutations without having to lock administration. + * 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 -ecs_cmd_t* flecs_cmd_alloc( - ecs_stage_t *stage) -{ - ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->commands, - ecs_cmd_t); - ecs_os_zeromem(cmd); - return cmd; -} +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 -ecs_cmd_t* flecs_cmd_new( - ecs_stage_t *stage, - ecs_entity_t e, - bool is_delete, - bool can_batch) +void flecs_filter_error( + const ecs_filter_finalize_ctx_t *ctx, + const char *fmt, + ...) { - if (e) { - ecs_vec_t *cmds = &stage->commands; - ecs_cmd_entry_t *entry = flecs_sparse_try_t( - &stage->cmd_entries, ecs_cmd_entry_t, e); - int32_t cur = ecs_vec_count(cmds); - if (entry) { - int32_t last = entry->last; - if (entry->last == -1) { - /* Entity was deleted, don't insert command */ - return NULL; - } + va_list args; + va_start(args, fmt); - if (can_batch) { - 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 if (can_batch || is_delete) { - entry = flecs_sparse_ensure_t(&stage->cmd_entries, - ecs_cmd_entry_t, e); - entry->first = cur; - } - if (can_batch) { - entry->last = cur; - } - if (is_delete) { - /* Prevent insertion of more commands for entity */ - entry->last = -1; - } + 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); - return flecs_cmd_alloc(stage); + va_end(args); } static -void flecs_stages_merge( - ecs_world_t *world, - bool force_merge) +int flecs_term_id_finalize_flags( + ecs_term_id_t *term_id, + ecs_filter_finalize_ctx_t *ctx) { - 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); + if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { + flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); + return -1; } - 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); + 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; } } } - flecs_eval_component_monitors(world); + if (term_id->flags & EcsParent) { + term_id->flags |= EcsUp; + term_id->trav = EcsChildOf; + } - if (measure_frame_time) { - world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); + if ((term_id->flags & EcsCascade) && !(term_id->flags & (EcsUp|EcsDown))) { + term_id->flags |= EcsUp; } - world->info.merge_count_total ++; + if ((term_id->flags & (EcsUp|EcsDown)) && !term_id->trav) { + term_id->trav = EcsIsA; + } - /* If stage is asynchronous, deferring is always enabled */ - if (stage->async) { - flecs_defer_begin(world, stage); + if (term_id->trav && !(term_id->flags & EcsTraverseFlags)) { + term_id->flags |= EcsUp; } - - ecs_log_pop_3(); -} -static -void flecs_stage_auto_merge( - ecs_world_t *world) -{ - flecs_stages_merge(world, false); + return 0; } 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) +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) { - 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; -} + const char *name = term_id->name; + if (!name) { + return 0; + } -bool flecs_defer_cmd( - ecs_stage_t *stage) -{ - if (stage->defer) { - return (stage->defer > 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; } - stage->defer ++; - return false; -} + ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); -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(stage, entity, false, true); - if (cmd) { - cmd->kind = EcsOpModified; - cmd->id = id; - cmd->entity = entity; + if (ecs_identifier_is_0(name)) { + if (term_id->id) { + flecs_filter_error(ctx, "name '0' does not match entity id"); + return -1; } - return true; + return 0; } - 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, entity, false, false); - if (cmd) { - cmd->kind = EcsOpClone; - cmd->id = src; - cmd->entity = entity; - cmd->is._1.clone_value = clone_value; - } - return true; + ecs_entity_t e = ecs_lookup_symbol(world, name, true, true); + if (scope && !e) { + e = ecs_lookup_child(world, scope, name); } - 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, entity, false, false); - if (cmd) { - cmd->kind = EcsOpPath; - cmd->entity = entity; - cmd->id = parent; - cmd->is._1.value = ecs_os_strdup(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; } - 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, entity, true, false); - if (cmd) { - cmd->kind = EcsOpDelete; - cmd->entity = entity; - } - return true; + 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; } - 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(stage, entity, false, true); - if (cmd) { - cmd->kind = EcsOpClear; - cmd->entity = entity; + 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; } - return true; + + 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 false; + + return 0; } -bool flecs_defer_on_delete_action( - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action) +static +int flecs_term_ids_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_alloc(stage); - cmd->kind = EcsOpOnDeleteAction; - cmd->id = id; - cmd->entity = action; - return true; + 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; } - 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, entity, false, false); - if (cmd) { - cmd->kind = enable ? EcsOpEnable : EcsOpDisable; - cmd->entity = entity; - cmd->id = id; + /* 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; + } } - 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)); + /* Traverse Self by default for pair target */ + if (!(second->flags & EcsTraverseFlags)) { + second->flags |= EcsSelf; + } - /* Use ecs_new_id as this is thread safe */ - int i; - for (i = 0; i < count; i ++) { - ids[i] = ecs_new_id(world); - } + /* Source defaults to This */ + if ((src->id == 0) && (src->name == NULL) && !(src->flags & EcsIsEntity)) { + src->id = EcsThis; + src->flags |= EcsIsVariable; + } - *ids_out = ids; + /* Initialize term identifier flags */ + if (flecs_term_id_finalize_flags(src, ctx)) { + return -1; + } + if (flecs_term_id_finalize_flags(first, ctx)) { + return -1; + } - /* Store data in op */ - ecs_cmd_t *cmd = flecs_cmd_alloc(stage); - if (cmd) { - cmd->kind = EcsOpBulkNew; - cmd->id = id; - cmd->is._n.entities = ids; - cmd->is._n.count = count; - } + if (flecs_term_id_finalize_flags(second, ctx)) { + return -1; + } - return true; + /* Lookup term identifiers by name */ + if (flecs_term_id_lookup(world, 0, src, term->move, ctx)) { + return -1; } - return false; + 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; } -bool flecs_defer_add( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) +static +ecs_entity_t flecs_term_id_get_entity( + const ecs_term_id_t *term_id) { - if (flecs_defer_cmd(stage)) { - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); - if (cmd) { - cmd->kind = EcsOpAdd; - cmd->id = id; - cmd->entity = entity; + 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; } - return true; + } else { + return 0; /* Term id is uninitialized */ } - return false; } -bool flecs_defer_remove( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) +static +int flecs_term_populate_id( + ecs_term_t *term) { - if (flecs_defer_cmd(stage)) { - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); - if (cmd) { - cmd->kind = EcsOpRemove; - cmd->id = id; - cmd->entity = entity; - } - return true; + 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; } - return false; + 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; } -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, - bool need_value) +static +int flecs_term_populate_from_id( + const ecs_world_t *world, + ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); - if (!cmd) { - if (need_value) { - /* Entity is deleted by a previous command, but we still need to - * return a temporary storage to the application. */ - cmd_kind = EcsOpSkip; - } else { - /* No value needs to be returned, we can drop the command */ - return NULL; - } + 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; } - /* 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 (term->id_flags && term->id_flags != role) { + flecs_filter_error(ctx, "mismatch between term.id & term.id_flags"); + return -1; } - /* If the id isn't associated with a type, we can't set anything */ - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + term->id_flags = role; - /* Make sure the size of the value equals the type size */ - ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); - size = ti->size; + if (ECS_HAS_ID_FLAG(term->id, PAIR)) { + first = ECS_PAIR_FIRST(term->id); + second = ECS_PAIR_SECOND(term->id); - /* 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, *storage_table; - 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 && (storage_table = table->storage_table)) { - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr, storage_table); - if (tr) { - /* Entity has the component */ - ecs_vec_t *column = &table->data.columns[tr->column]; - existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); + 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; + } } - /* Get existing value from storage */ - void *cmd_value = existing; - bool emplace = cmd_kind == EcsOpEmplace; + 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; + } + } - /* 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->defer_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; + 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; } - cmd = flecs_cmd_alloc(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 == EcsOpSet) { - cmd->kind = EcsOpAddModified; - } else { - cmd->kind = EcsOpAdd; + } else if (second) { + if (!(term->second.id = ecs_get_alive(world, second))) { + term->second.id = second; } - cmd->id = id; - cmd->entity = entity; } - return cmd_value; -error: - return NULL; + return 0; } -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage) +static +int flecs_term_verify_eq_pred( + const ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - /* 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_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; } - ecs_vec_clear(&stage->post_frame_actions); -} + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "both sides of operator cannot be a name"); + goto error; + } -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); + if ((src->flags & EcsIsEntity) && (second->flags & EcsIsEntity)) { + flecs_filter_error(ctx, "both sides of operator cannot be an entity"); + goto error; + } - stage->world = world; - stage->thread_ctx = world; - stage->auto_merge = true; - stage->async = false; + if (!(src->flags & EcsIsVariable)) { + flecs_filter_error(ctx, "left-hand of operator must be a variable"); + goto error; + } - flecs_stack_init(&stage->defer_stack); - 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); + if (first_id == EcsPredMatch && !(second->flags & EcsIsName)) { + flecs_filter_error(ctx, "right-hand of match operator must be a string"); + goto error; + } - ecs_allocator_t *a = &stage->allocator; - ecs_vec_init_t(a, &stage->commands, ecs_cmd_t, 0); - ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); - flecs_sparse_init_t(&stage->cmd_entries, &stage->allocator, - &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); + 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; } -void flecs_stage_fini( - ecs_world_t *world, - ecs_stage_t *stage) +static +int flecs_term_verify( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - (void)world; - ecs_poly_assert(world, ecs_world_t); - ecs_poly_assert(stage, ecs_stage_t); + 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; - /* Make sure stage has no unmerged data */ - ecs_assert(ecs_vec_count(&stage->commands) == 0, ECS_INTERNAL_ERROR, NULL); + if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { + flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; + } - ecs_poly_fini(stage, ecs_stage_t); + if (first->flags & EcsIsEntity) { + first_id = first->id; + } + if (second->flags & EcsIsEntity) { + second_id = second->id; + } - flecs_sparse_fini(&stage->cmd_entries); + if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { + return flecs_term_verify_eq_pred(term, ctx); + } - ecs_allocator_t *a = &stage->allocator; - ecs_vec_fini_t(a, &stage->commands, ecs_cmd_t); - 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); - flecs_stack_fini(&stage->defer_stack); - 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); -} + if (role != (id & ECS_ID_FLAGS_MASK)) { + flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; + } -void ecs_set_stage_count( - ecs_world_t *world, - int32_t stage_count) -{ - ecs_poly_assert(world, ecs_world_t); + 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 */ + } + } - /* World must have at least one default stage */ - ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), - ECS_INTERNAL_ERROR, NULL); + if (!ecs_term_id_is_set(src)) { + flecs_filter_error(ctx, "term.src is not initialized"); + return -1; + } - bool auto_merge = true; - 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; + if (!ecs_term_id_is_set(first)) { + flecs_filter_error(ctx, "term.first is not initialized"); + return -1; } - int32_t i, count = world->stage_count; - if (count && count != stage_count) { - ecs_stage_t *stages = world->stages; + 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; + } - 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]); + 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; } - ecs_os_free(world->stages); + 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 (stage_count) { - world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count); + 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); + } - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = &world->stages[i]; - flecs_stage_init(world, stage); - stage->id = i; + 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; + } + } + } - /* 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; + 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; + } + } } - } 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; + 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; + } } - 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_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( +static +int flecs_term_finalize( 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_term_t *term, + ecs_filter_finalize_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); + ctx->term = term; - flecs_process_pending_tables(world); + 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; - ecs_dbg_3("#[bold]readonly"); - ecs_log_push_3(); + if (term->id) { + if (flecs_term_populate_from_id(world, term, ctx)) { + return -1; + } + } - 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); + if (flecs_term_ids_finalize(world, term, ctx)) { + return -1; } - bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); + 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; + } - /* 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 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); + } - /* 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); + 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; + } } - return is_readonly; -} + if (!term->id) { + if (flecs_term_populate_id(term)) { + return -1; + } + } -void ecs_readonly_end( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); + /* 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)) { + term->oper = EcsAnd; + term->id = ecs_pair(EcsChildOf, 0); + term->second.id = 0; + term->second.flags |= EcsIsEntity; + term->second.flags &= ~EcsIsVariable; + } - /* After this it is safe again to mutate the world directly */ - ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); - ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); + ecs_entity_t first_id = 0; + if (term->first.flags & EcsIsEntity) { + first_id = term->first.id; + } - ecs_log_pop_3(); + term->idr = flecs_query_id_record_get(world, term->id); + ecs_flags32_t id_flags = term->idr ? term->idr->flags : 0; - flecs_stage_auto_merge(world); -error: - return; + 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; + } + } + + if (flecs_term_verify(world, term, ctx)) { + return -1; + } + + return 0; } -void ecs_merge( - ecs_world_t *world) +ecs_id_t flecs_to_public_id( + ecs_id_t id) { - 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; + if (ECS_PAIR_FIRST(id) == EcsUnion) { + return ecs_pair(ECS_PAIR_SECOND(id), EcsWildcard); + } else { + return id; + } } -void ecs_set_automerge( +ecs_id_t flecs_from_public_id( ecs_world_t *world, - bool auto_merge) + ecs_id_t id) { - /* 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 (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); } - - /* 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; } + + return id; } -bool ecs_stage_is_readonly( - const ecs_world_t *stage) +bool ecs_identifier_is_0( + const char *id) { - const ecs_world_t *world = ecs_get_world(stage); + return id[0] == '0' && !id[1]; +} - if (ecs_poly_is(stage, ecs_stage_t)) { - if (((ecs_stage_t*)stage)->async) { +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; } - } - if (world->flags & EcsWorldReadonly) { - if (ecs_poly_is(stage, ecs_world_t)) { - return true; + 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 (ecs_poly_is(stage, ecs_stage_t)) { + if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { + return false; + } + + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { return true; } } +error: return false; } -ecs_world_t* ecs_async_stage_new( - ecs_world_t *world) +bool ecs_id_is_pair( + ecs_id_t id) { - ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); - flecs_stage_init(world, stage); + return ECS_HAS_ID_FLAG(id, PAIR); +} - stage->id = -1; - stage->auto_merge = false; - stage->async = true; +bool ecs_id_is_wildcard( + ecs_id_t id) +{ + if ((id == EcsWildcard) || (id == EcsAny)) { + return true; + } - flecs_defer_begin(world, stage); + bool is_pair = ECS_IS_PAIR(id); + if (!is_pair) { + return false; + } - return (ecs_world_t*)stage; -} + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); -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; + return (first == EcsWildcard) || (second == EcsWildcard) || + (first == EcsAny) || (second == EcsAny); } -bool ecs_stage_is_async( - ecs_world_t *stage) +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t id) { - if (!stage) { + if (!id) { return false; } - - if (!ecs_poly_is(stage, ecs_stage_t)) { + if (ecs_id_is_wildcard(id)) { return false; } - return ((ecs_stage_t*)stage)->async; -} + 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; + } + } -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; + return true; } -/** - * @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) +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, + ecs_id_t id) { - return ECS_ALIGN(size, 16); + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + return idr->flags; + } else { + return 0; + } } -static -ecs_size_t flecs_allocator_size_hash( - ecs_size_t size) +bool ecs_term_id_is_set( + const ecs_term_id_t *id) { - return size >> 4; + return id->id != 0 || id->name != NULL || id->flags & EcsIsEntity; } -void flecs_allocator_init( - ecs_allocator_t *a) +bool ecs_term_is_initialized( + const ecs_term_t *term) { - 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); + return term->id != 0 || ecs_term_id_is_set(&term->first); } -void flecs_allocator_fini( - ecs_allocator_t *a) +bool ecs_term_match_this( + const ecs_term_t *term) { - 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); + return (term->src.flags & EcsIsVariable) && (term->src.id == EcsThis); } -ecs_block_allocator_t* flecs_allocator_get( - ecs_allocator_t *a, - ecs_size_t size) +bool ecs_term_match_0( + const ecs_term_t *term) { - 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; + return (term->src.id == 0) && (term->src.flags & EcsIsEntity); } -char* flecs_strdup( - ecs_allocator_t *a, - const char* str) +int ecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term) { - 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; + ecs_filter_finalize_ctx_t ctx = {0}; + ctx.world = world; + ctx.term = term; + return flecs_term_finalize(world, term, &ctx); } -void flecs_strfree( - ecs_allocator_t *a, - char* str) +ecs_term_t ecs_term_copy( + const ecs_term_t *src) { - ecs_size_t len = ecs_os_strlen(str); - flecs_free_n(a, char, len + 1, str); + 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; } -void* flecs_dup( - ecs_allocator_t *a, - ecs_size_t size, - const void *src) +ecs_term_t ecs_term_move( + ecs_term_t *src) { - ecs_block_allocator_t *ba = flecs_allocator_get(a, size); - if (ba) { - void *dst = flecs_balloc(ba); - ecs_os_memcpy(dst, src, size); + 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 { - return NULL; + ecs_term_t dst = ecs_term_copy(src); + dst.move = false; + return dst; } } -/** - * @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)) +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); -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; + term->first.name = NULL; + term->src.name = NULL; + term->second.name = NULL; + term->name = NULL; +} static -ecs_page_t* flecs_sparse_page_new( - ecs_sparse_t *sparse, - int32_t page_index) +ecs_term_t* flecs_filter_or_other_type( + ecs_filter_t *f, + int32_t t) { - 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_term_t *term = &f->terms[t]; + ecs_term_t *first = NULL; + while (t--) { + if (f->terms[t].oper != EcsOr) { + break; + } + first = &f->terms[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 (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 (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); + if (first_type == term_type) { + return NULL; + } + return first; } 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) +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *f) { - ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); - if (page && page->sparse) { - return page; - } + 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; - return flecs_sparse_page_new(sparse, page_index); -} + ecs_filter_finalize_ctx_t ctx = {0}; + ctx.world = world; + ctx.filter = f; -static -void flecs_sparse_grow_dense( - ecs_sparse_t *sparse) -{ - ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); -} + f->flags |= EcsFilterMatchOnlyThis; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ctx.term_index = i; + if (flecs_term_finalize(world, term, &ctx)) { + return -1; + } -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; -} + 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 ++; + } -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; -} + if (term->oper == EcsOr || (i && term[-1].oper == EcsOr)) { + ecs_term_t *first = flecs_filter_or_other_type(f, i); + if (first) { + filter_terms ++; + if (first == &term[-1]) { + filter_terms ++; + } + } + } -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); -} + term->field_index = field_count - 1; -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; -} + if (ecs_term_match_this(term)) { + ECS_BIT_SET(f->flags, EcsFilterMatchThis); + } else { + ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); + } -static -uint64_t flecs_sparse_get_id( - const ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); - return sparse->max_id; -} + if (term->id == EcsPrefab) { + ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); + } + if (term->id == EcsDisabled && (term->src.flags & EcsSelf)) { + ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); + } -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; -} + if (ECS_BIT_IS_SET(f->flags, EcsFilterNoData)) { + term->inout = EcsInOutNone; + } + + if (term->oper == EcsNot && term->inout == EcsInOutDefault) { + term->inout = EcsInOutNone; + } -/* 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); + if (term->inout == EcsInOutNone) { + filter_terms ++; + } else if (term->idr) { + if (!term->idr->type_info && !(term->idr->flags & EcsIdUnion)) { + filter_terms ++; + } + } else if (ecs_id_is_tag(world, term->id)) { + 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 */ + filter_terms ++; + } + } + 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; + } + } - 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; -} + if (term->oper != EcsNot || !ecs_term_match_this(term)) { + ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); + } -/* 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 ++; + if (term->idr) { + if (ecs_os_has_threading()) { + ecs_os_ainc(&term->idr->keep_alive); + } else { + term->idr->keep_alive ++; + } + } - 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); + 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 '{'"); + } } -} -/* 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; + if (scope_nesting != 0) { + flecs_filter_error(&ctx, "missing '}'"); + return -1; } - 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; + if (term_count && (terms[term_count - 1].oper == EcsOr)) { + flecs_filter_error(&ctx, "last term of filter can't have OR operator"); + return -1; + } - return DATA(page->data, sparse->size, offset); -} + f->field_count = field_count; -/* 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]; + 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; - 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); -} + if (term->oper == EcsOr || (i && (term[-1].oper == EcsOr))) { + if (flecs_filter_or_other_type(f, i)) { + f->sizes[field] = 0; + continue; + } + } -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; + 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); + } else if (idr->type_info) { + f->sizes[field] = idr->type_info->size; + } + } + } 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); + } else { + const ecs_type_info_t *ti = ecs_get_type_info( + world, term->id); + if (ti) { + f->sizes[field] = ti->size; + } + } + } + } + } else { + f->sizes = NULL; + } - 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; + if (filter_terms >= term_count) { + ECS_BIT_SET(f->flags, EcsFilterNoData); + } - /* 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; + ECS_BIT_COND(f->flags, EcsFilterHasCondSet, cond_set); - result->count = 1; + return 0; } -void flecs_sparse_clear( - ecs_sparse_t *sparse) +/* 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_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(poly, ecs_filter_t); - 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); - } + 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)); } - - 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) +/* Implementation for dtor mixin */ +static +void flecs_filter_fini( + ecs_filter_t *filter) { - 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]); - } + 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]); + } - ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); - ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); -} + if (filter->terms_owned) { + /* Memory allocated for both terms & sizes */ + ecs_os_free(filter->terms); + } else { + ecs_os_free(filter->sizes); + } + } -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_sparse_new_index(sparse); -} + filter->terms = NULL; -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)); + if (filter->owned) { + ecs_os_free(filter); + } } -uint64_t flecs_sparse_last_id( - const ecs_sparse_t *sparse) +void ecs_filter_fini( + ecs_filter_t *filter) { - 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]; + if (filter->owned && filter->entity) { + /* If filter is associated with entity, use poly dtor path */ + ecs_delete(filter->world, filter->entity); + } else { + flecs_filter_fini(filter); + } } -void* flecs_sparse_ensure( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +ecs_filter_t* ecs_filter_init( + ecs_world_t *world, + const ecs_filter_desc_t *desc) { - 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; + 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); - 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]; + 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 (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; + 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->owned = true; + } + if (!storage_terms) { + f->terms_owned = true; + } - /* First unused element is now last used element */ - sparse->count ++; - } else { - /* Dense is already alive, nothing to be done */ - } + ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); + ECS_BIT_SET(f->flags, EcsFilterMatchAnything); + f->flags |= desc->flags; + f->world = world; - /* 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 + /* 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 { - /* Element is not paired yet. Must add a new element to dense array */ - flecs_sparse_grow_dense(sparse); + ecs_check(term_count != 0, ECS_INVALID_PARAMETER, NULL); + } - 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 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}; + int32_t expr_size = 0; - /* If index is larger than max id, update max id */ - if (index >= flecs_sparse_get_id(sparse)) { - flecs_sparse_set_id(sparse, index); + if (entity) { + name = ecs_get_name(world, entity); } - 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); + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } + + 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); + } + + expr_terms[expr_count ++] = term; + if (ptr[0] == '\n') { + break; + } } - flecs_sparse_assign_index(page, dense_array, index, count); - dense_array[count] |= gen; + if (!ptr) { + /* Set terms in filter object to make sur they get cleaned up */ + f->terms = expr_terms; + f->term_count = expr_count; + f->terms_owned = true; + goto error; + } +#else + (void)expr; + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif } - return DATA(page->data, sparse->size, offset); -} + /* If storage is provided, make sure it's large enough */ + ecs_check(!storage_terms || storage_count >= (term_count + expr_count), + ECS_INVALID_PARAMETER, NULL); -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; + if (term_count || expr_count) { + /* Allocate storage for terms and sizes array */ + if (!storage_terms) { + ecs_assert(f->terms_owned == true, ECS_INTERNAL_ERROR, NULL); + f->term_count = term_count + expr_count; + ecs_size_t terms_size = ECS_SIZEOF(ecs_term_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); + f->sizes = ECS_OFFSET(f->terms, terms_size); + } else { + f->terms = storage_terms; + f->term_count = storage_count; + f->sizes = ecs_os_calloc_n(ecs_size_t, term_count); + } - 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; + /* 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; + } - 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); + /* 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); + } - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - flecs_sparse_assign_index(page, dense_array, index, count); + /* Ensure all fields are consistent and properly filled out */ + if (ecs_filter_finalize(world, f)) { + goto error; } - return DATA(page->data, sparse->size, offset); + /* 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->owned) { + 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 flecs_sparse_remove( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src) { - 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) { + if (src == dst) { return; } - uint64_t gen = flecs_sparse_strip_generation(&index); - int32_t offset = OFFSET(index); - int32_t dense = page->sparse[offset]; + if (src) { + *dst = *src; - 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; - } + 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; + dst->terms = ecs_os_malloc(terms_size + sizes_size); + dst->sizes = ECS_OFFSET(dst->terms, terms_size); + dst->terms_owned = true; + ecs_os_memcpy_n(dst->sizes, src->sizes, int32_t, term_count); - /* 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; + for (i = 0; i < term_count; i ++) { + dst->terms[i] = ecs_term_copy(&src->terms[i]); } - - /* 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; + ecs_os_memset_t(dst, 0, ecs_filter_t); } } -void flecs_sparse_set_generation( - ecs_sparse_t *sparse, - uint64_t index) +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src) { - 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 (src == dst) { + return; + } - if (dense) { - /* Increase generation */ - ecs_vec_get_t(&sparse->dense, uint64_t, dense)[0] = index_w_gen; + if (src) { + *dst = *src; + if (src->terms_owned) { + dst->terms = src->terms; + dst->sizes = src->sizes; + dst->terms_owned = true; + } else { + ecs_filter_copy(dst, src); + } + src->terms = NULL; + src->sizes = NULL; + src->term_count = 0; } else { - /* Element is not paired and thus not alive, nothing to be done */ + ecs_os_memset_t(dst, 0, ecs_filter_t); } } -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) +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) { - ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); - if (!page || !page->sparse) { - return false; + 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; } - int32_t offset = OFFSET(index); - int32_t dense = page->sparse[offset]; - if (!dense || (dense >= sparse->count)) { - return false; + 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; } - 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; - } + 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"); + } - ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return true; -} + if (id->trav && (id->trav != EcsIsA)) { + ecs_strbuf_list_push(buf, "(", ""); -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; - } + char *rel_path = ecs_get_fullpath(world, id->trav); + ecs_strbuf_appendstr(buf, rel_path); + ecs_os_free(rel_path); - int32_t offset = OFFSET(index); - int32_t dense = page->sparse[offset]; - if (!dense || (dense >= sparse->count)) { - return NULL; - } + ecs_strbuf_list_pop(buf, ")"); + } - 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_strbuf_list_pop(buf, ""); } - - 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) +static +void flecs_term_str_w_strbuf( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf, + int32_t t) { - 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); + const ecs_term_id_t *src = &term->src; + const ecs_term_id_t *second = &term->second; - 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; + uint8_t def_src_mask = EcsSelf|EcsUp; + uint8_t def_first_mask = EcsSelf; + uint8_t def_second_mask = EcsSelf; - 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); -} + 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); -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; + if (term->first.id == EcsScopeOpen) { + ecs_strbuf_appendlit(buf, "{"); + return; + } else if (term->first.id == EcsScopeClose) { + ecs_strbuf_appendlit(buf, "}"); + return; } - int32_t offset = OFFSET(index); - int32_t dense = page->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; + 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] "); + } } - 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; + if (term->first.flags & EcsIsEntity && term->first.id != 0) { + if (ecs_has_id(world, term->first.id, EcsDontInherit)) { + def_src_mask = EcsSelf; + } } - return sparse->count - 1; -} + if (term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendlit(buf, "?"); + } -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]); + 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 { - return NULL; - } -} + 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, '|'); + } -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); + 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, ")"); + } } -int32_t ecs_sparse_count( - const ecs_sparse_t *sparse) +char* ecs_term_str( + const ecs_world_t *world, + const ecs_term_t *term) { - return flecs_sparse_count(sparse); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + flecs_term_str_w_strbuf(world, term, &buf, 0); + return ecs_strbuf_get(&buf); } -void* ecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index) +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) { - return flecs_sparse_get_dense(sparse, elem_size, index); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); -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); -} + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; -/** - * @file datastructures/switch_list.c - * @brief Interleaved linked list for storing mutually exclusive values. - * - * Datastructure that stores N interleaved linked lists in an array. - * This allows for efficient storage of elements with mutually exclusive values. - * Each linked list has a header element which points to the index in the array - * that stores the first node of the list. Each list node points to the next - * array element. - * - * The datastructure 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. - */ + 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 + , */ + } + } + } -#ifdef FLECS_SANITIZE -static -void flecs_switch_verify_nodes( - ecs_switch_header_t *hdr, - ecs_switch_node_t *nodes) -{ - if (!hdr) { - return; - } + flecs_term_str_w_strbuf(world, term, &buf, i); - 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 ++; + 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, ", "); + } + } + } + } } - ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); + return ecs_strbuf_get(&buf); +error: + return 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) +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter) { - if (value == 0) { - return NULL; - } - return (ecs_switch_header_t*)ecs_map_get(&sw->hdrs, value); + return flecs_filter_str(world, filter, NULL, NULL); } -static -ecs_switch_header_t *flecs_switch_ensure_header( - ecs_switch_t *sw, - uint64_t value) +int32_t ecs_filter_find_this_var( + const ecs_filter_t *filter) { - 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; + 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; } - return node; +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 -void flecs_switch_remove_node( - ecs_switch_header_t *hdr, - ecs_switch_node_t *nodes, - ecs_switch_node_t *node, - int32_t element) +bool is_any_pair( + ecs_id_t id) { - 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; + if (!ECS_HAS_ID_FLAG(id, PAIR)) { + return false; } - /* 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; + if (ECS_PAIR_FIRST(id) == EcsAny) { + return true; + } + if (ECS_PAIR_SECOND(id) == EcsAny) { + return true; } - /* Decrease count of current header */ - hdr->count --; - ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); + return false; } -void flecs_switch_init( - ecs_switch_t *sw, - ecs_allocator_t *allocator, - int32_t elements) +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) { - 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); + (void)column_out; - ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); - uint64_t *values = ecs_vec_first(&sw->values); + const ecs_type_t *type = ecs_get_type(world, type_id); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); - int i; - for (i = 0; i < elements; i ++) { - nodes[i].prev = -1; - nodes[i].next = -1; - values[i] = 0; + 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; + } } -} -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); -} + if (oper == EcsAndFrom) { + if (id_out) { + id_out[0] = type_id; + } + return true; + } else + if (oper == EcsOrFrom) { + return false; + } -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); + return false; } -void flecs_switch_add( - ecs_switch_t *sw) +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) { - 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; -} + 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; -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_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; } - 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; + 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); } -} -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); -} + /* 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; + } -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; + match_table = ecs_get_table(world, src_id); + 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); } - 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; + if (!match_table) { + return false; } - 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); + ecs_entity_t source = 0; - /* 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 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; + } - if (cur_hdr) { - flecs_switch_remove_node(cur_hdr, nodes, node, element); + /* Remove base 1 offset */ + column --; } - /* Now update the node itself by adding it as the first node of dst */ - node->prev = -1; - values[element] = value; + /* Find location, source and id of match in table type */ + ecs_table_record_t *tr = 0; + bool is_any = is_any_pair(id); - if (dst_hdr) { - node->next = dst_hdr->element; + column = flecs_search_relation_w_idr(world, match_table, + column, id, src->trav, src->flags, &source, id_out, &tr, term->idr); - /* 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; + if (tr && match_index_out) { + if (!is_any) { + match_index_out[0] = tr->count; + } else { + match_index_out[0] = 1; } - - 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]; + bool result = column != -1; - /* 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); + if (oper == EcsNot) { + if (match_index_out) { + match_index_out[0] = 1; + } + result = !result; + } - flecs_switch_verify_nodes(hdr, nodes); - flecs_switch_remove_node(hdr, nodes, node, elem); + if (oper == EcsOptional) { + result = true; } - 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 ((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 (prev != -1) { - ecs_switch_node_t *n = &nodes[prev]; - n->next = elem; + if (!result) { + if (iter_flags & EcsFilterPopulate) { + column = 0; } 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; - } + return false; } } - /* Remove element from arrays */ - ecs_vec_remove_t(&sw->nodes, ecs_switch_node_t, elem); - ecs_vec_remove_t(&sw->values, uint64_t, elem); -} + if (!ecs_term_match_this(term)) { + if (!source) { + source = src_id; + } + } -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); + if (id_out && column < 0) { + id_out[0] = id; + } - uint64_t *values = ecs_vec_first(&sw->values); - return values[element]; -} - -ecs_vec_t* flecs_switch_values( - const ecs_switch_t *sw) -{ - return (ecs_vec_t*)&sw->values; -} + if (column_out) { + if (column >= 0) { + column ++; + if (source != 0) { + column *= -1; + } + column_out[0] = column; + } else { + column_out[0] = 0; + } + } -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; + if (subject_out) { + subject_out[0] = source; } - return hdr->count; + return result; } -void flecs_switch_swap( - ecs_switch_t *sw, - int32_t elem_1, - int32_t elem_2) +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) { - 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); -} + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + int32_t match_count = 1; + bool result = true; -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; + if (matches_left) { + match_count = *matches_left; } - return hdr->element; -} + 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; + } + } -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_term_id_t *src = &term->src; + const ecs_table_t *match_table = table; + int32_t t_i = term->field_index; - ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); + ecs_entity_t src_id = src->id; + if (!src_id) { + if (ids) { + ids[t_i] = term->id; + } + continue; + } - return nodes[element].next; -} + if (!ecs_term_match_this(term)) { + match_table = ecs_get_table(world, src_id); + } 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 */ + } + } -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); + 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; + } } - 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); + if (matches_left) { + *matches_left = match_count; } - return page; + return true; } -void flecs_entity_index_init( - ecs_allocator_t *allocator, - ecs_entity_index_t *index) +static +void term_iter_init_no_data( + ecs_term_iter_t *iter) { - 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)); + iter->term = (ecs_term_t){ .field_index = -1 }; + iter->self_index = NULL; + iter->index = 0; } -void flecs_entity_index_fini( - ecs_entity_index_t *index) +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) { - 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]); + 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); } -#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; + 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; + } } -ecs_record_t* flecs_entity_index_get( - const ecs_entity_index_t *index, - uint64_t entity) +static +void term_iter_init_wildcard( + const ecs_world_t *world, + ecs_term_iter_t *iter, + bool empty_tables) { - 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; + 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); } -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; - } +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; - ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, - ecs_entity_index_page_t*, page_index)[0]; - if (!page) { - return NULL; - } + iter->term = *term; - ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; - if (!r->dense) { - return NULL; + if (src->flags & EcsSelf) { + iter->self_index = term->idr; + if (!iter->self_index) { + iter->self_index = flecs_query_id_record_get(world, term->id); + } } - return r; -} + if (src->flags & EcsUp) { + iter->set_index = flecs_id_record_get(world, + ecs_pair(src->trav, EcsWildcard)); + } -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; - } + ecs_id_record_t *idr; + if (iter->self_index) { + idr = iter->cur = iter->self_index; + } else { + idr = iter->cur = iter->set_index; } - return r; + + term_iter_init_w_idr(term, iter, idr, empty_tables); } -ecs_record_t* flecs_entity_index_ensure( - ecs_entity_index_t *index, - uint64_t entity) +ecs_iter_t ecs_term_iter( + const ecs_world_t *stage, + ecs_term_t *term) { - 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]; + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - 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; - } + const ecs_world_t *world = ecs_get_world(stage); - ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); + flecs_process_pending_tables(world); - /* 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); + if (ecs_term_finalize(world, term)) { + ecs_throw(ECS_INVALID_PARAMETER, NULL); + } - r_swap->dense = dense; - r->dense = index->alive_count; - ids[dense] = e_swap; - ids[index->alive_count ++] = entity; + 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 + }; - ecs_assert(flecs_entity_index_is_alive(index, entity), - ECS_INTERNAL_ERROR, NULL); + /* 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 accomodate 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 r; + return it; +error: + return (ecs_iter_t){ 0 }; } -void flecs_entity_index_remove( - ecs_entity_index_t *index, - uint64_t entity) +ecs_iter_t ecs_term_chain_iter( + const ecs_iter_t *chain_it, + ecs_term_t *term) { - 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; + 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); } - 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); + 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 + }; - 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); -} + flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); -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; - } + term_iter_init(world, term, &it.priv.iter.term, false); + + return it; +error: + return (ecs_iter_t){ 0 }; } -uint64_t flecs_entity_index_get_generation( - const ecs_entity_index_t *index, - uint64_t entity) +ecs_iter_t ecs_children( + const ecs_world_t *world, + ecs_entity_t parent) { - 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; - } + return ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(parent) }); } -bool flecs_entity_index_is_alive( - const ecs_entity_index_t *index, - uint64_t entity) +bool ecs_children_next( + ecs_iter_t *it) { - return flecs_entity_index_try_get(index, entity) != NULL; + return ecs_term_next(it); } -bool flecs_entity_index_is_valid( - const ecs_entity_index_t *index, - uint64_t entity) +static +const ecs_table_record_t *flecs_term_iter_next_table( + ecs_term_iter_t *iter) { - 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; + ecs_id_record_t *idr = iter->cur; + if (!idr) { + return NULL; } - /* If the id exists, it must be alive */ - return r->dense < index->alive_count; + return flecs_table_cache_next(&iter->it, ecs_table_record_t); } -bool flecs_entity_index_exists( - const ecs_entity_index_t *index, - uint64_t entity) +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) { - return flecs_entity_index_try_get_any(index, entity) != NULL; -} + ecs_term_id_t *src = &term->src; -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]; + /* Test if following the relationship finds the id */ + int32_t index = flecs_search_relation_w_idr(world, table, 0, + term->id, src->trav, src->flags, source, id, 0, term->idr); + + if (index == -1) { + *source = 0; + return false; } - /* Create new id */ - uint32_t id = (uint32_t)++ index->max_id; - ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; + ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); - 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); + *column = (index + 1) * -1; - return id; + return true; } -uint64_t* flecs_entity_index_new_ids( - ecs_entity_index_t *index, - int32_t count) +static +bool flecs_term_iter_next( + ecs_world_t *world, + ecs_term_iter_t *iter, + bool match_prefab, + bool match_disabled) { - 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); - } + ecs_table_t *table = iter->table; + ecs_entity_t source = 0; + const ecs_table_record_t *tr; + ecs_term_t *term = &iter->term; - /* 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; - } + 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]; + } + } + } - index->alive_count = new_count; - return ecs_vec_get_t(&index->dense, uint64_t, alive_count); -} + 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); + } + } -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); -} + if (!tr) { + return false; + } + } -int32_t flecs_entity_index_count( - const ecs_entity_index_t *index) -{ - return index->alive_count - 1; -} + table = tr->hdr.table; + if (table->_->traversable_count) { + iter->observed_table_count ++; + } -int32_t flecs_entity_index_size( - const ecs_entity_index_t *index) -{ - return ecs_vec_count(&index->dense) - 1; -} + if (!match_prefab && (table->flags & EcsTableIsPrefab)) { + continue; + } -int32_t flecs_entity_index_not_alive_count( - const ecs_entity_index_t *index) -{ - return ecs_vec_count(&index->dense) - index->alive_count; -} + if (!match_disabled && (table->flags & EcsTableIsDisabled)) { + continue; + } -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); + 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]); } - } - ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); + 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; + } + } - index->alive_count = 1; - index->max_id = 0; -} + if (!flecs_term_iter_find_superset( + world, table, term, &source, &iter->id, &iter->column)) + { + continue; + } -const uint64_t* flecs_entity_index_ids( - const ecs_entity_index_t *index) -{ - return ecs_vec_get_t(&index->dense, uint64_t, 1); -} + /* 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; + } -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); - } + break; + } while (true); - dst->max_id = src->max_id; + iter->subject = source; - ecs_assert(src->alive_count == dst->alive_count, ECS_INTERNAL_ERROR, NULL); + return true; } -void flecs_entity_index_copy( - ecs_entity_index_t *dst, - const ecs_entity_index_t *src) +static +bool flecs_term_iter_set_table( + ecs_world_t *world, + ecs_term_iter_t *iter, + ecs_table_t *table) { - if (!src) { - return; + 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]); + } } - 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; + 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; + } } - flecs_entity_index_clear(dst); - flecs_entity_index_copy_intern(dst, src); -} - -/** - * @file datastructures/name_index.c - * @brief Data structure for resolving 64bit keys by string (name). - */ + if (!tr) { + return false; + } + /* Populate fields as usual */ + iter->table = table; + iter->cur_match = 0; -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; + return true; } -static -int flecs_name_index_compare( - const void *ptr1, - const void *ptr2) +bool ecs_term_next( + ecs_iter_t *it) { - 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); - } + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); - return ecs_os_memcmp(str1->value, str2->value, len1); -} + flecs_iter_validate(it); -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); -} + 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; -void flecs_name_index_init_if( - ecs_hashmap_t *hm, - ecs_allocator_t *allocator) -{ - if (!hm->compare) { - flecs_name_index_init(hm, allocator); - } -} + it->ids = &iter->id; + it->sources = &iter->subject; + it->columns = &iter->column; + it->terms = &iter->term; + it->sizes = &iter->size; + it->ptrs = &iter->ptr; -bool flecs_name_index_is_init( - const ecs_hashmap_t *hm) -{ - return hm->compare != NULL; -} + ecs_iter_t *chain_it = it->chain_it; + if (chain_it) { + ecs_iter_next_action_t next = chain_it->next; + bool match; -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; -} + do { + if (!next(chain_it)) { + goto done; + } -void flecs_name_index_fini( - ecs_hashmap_t *map) -{ - flecs_hashmap_fini(map); -} + 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; -void flecs_name_index_free( - ecs_hashmap_t *map) -{ - if (map) { - flecs_name_index_fini(map); - flecs_bfree(map->hashmap_allocator, map); - } -} + } else { + if (!flecs_term_iter_next(world, iter, + (term->flags & EcsTermMatchPrefab) != 0, + (term->flags & EcsTermMatchDisabled) != 0)) + { + goto done; + } -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; -} + table = iter->table; -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); + /* 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); } - if (!hash) { - hash = flecs_hash(name, length); - } else { - ecs_assert(hash == flecs_hash(name, length), 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; +} - return (ecs_hashed_string_t) { - .value = (char*)name, - .length = length, - .hash = hash - }; +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; } -const uint64_t* flecs_name_index_find_ptr( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash) +int32_t ecs_filter_pivot_term( + const ecs_world_t *world, + const ecs_filter_t *filter) { - 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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); - int32_t i, count = ecs_vec_count(&b->keys); + 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 < count; i ++) { - ecs_hashed_string_t *key = &keys[i]; - ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_id_t id = term->id; - if (hs.length != key->length) { + if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { 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; + if (!ecs_term_match_this(term)) { + continue; } - } - return NULL; -} + 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 */ + } -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]; + 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; + } + } } - 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; + if (self_pivot_term != -1) { + pivot_term = self_pivot_term; } - 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; - } - } + return pivot_term; +error: + return -2; } -void flecs_name_index_update_name( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash, - const char *name) +void flecs_filter_apply_iter_flags( + ecs_iter_t *it, + const ecs_filter_t *filter) { - 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 = (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); + 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)); } -void flecs_name_index_ensure( - ecs_hashmap_t *map, - uint64_t id, - const char *name, - ecs_size_t length, - uint64_t hash) +ecs_iter_t flecs_filter_iter_w_flags( + const ecs_world_t *stage, + const ecs_filter_t *filter, + ecs_flags32_t flags) { - ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); + 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); - 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); - } + if (!(flags & EcsIterMatchVar)) { + flecs_process_pending_tables(world); } - flecs_hashmap_result_t hmr = flecs_hashmap_ensure( - map, &key, uint64_t); - *((uint64_t*)hmr.value) = id; -error: - return; -} + ecs_iter_t it = { + .real_world = ECS_CONST_CAST(ecs_world_t*, world), + .world = ECS_CONST_CAST(ecs_world_t*, stage), + .terms = filter ? filter->terms : NULL, + .next = ecs_filter_next, + .flags = flags, + .sizes = filter->sizes + }; -// 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 + ecs_filter_iter_t *iter = &it.priv.iter.filter; + iter->pivot_term = -1; -/* 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 + flecs_init_filter_iter(&it, filter); + flecs_filter_apply_iter_flags(&it, filter); -//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 + /* 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); -//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 -} + pivot_term = ecs_filter_pivot_term(world, filter); + iter->kind = EcsIterEvalTables; + iter->pivot_term = pivot_term; -//multiply and xor mix function, aka MUM -static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; } + 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; + } + } -//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 + ECS_BIT_COND(it.flags, EcsIterNoData, + ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); -//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];} + 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; -//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; + /* Set variable name array */ + it.variable_names = ECS_CONST_CAST(char**, filter->variable_names); } - 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}; + flecs_iter_init(stage, &it, flecs_iter_cache_all); -uint64_t flecs_hash( - const void *data, - ecs_size_t length) -{ - return wyhash(data, flecs_ito(size_t, length), 0, _wyp); + return it; +error: + return (ecs_iter_t){ 0 }; } -/** - * @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) +ecs_iter_t ecs_filter_iter( + const ecs_world_t *stage, + const ecs_filter_t *filter) { - 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); - } + return flecs_filter_iter_w_flags(stage, filter, 0); } -void flecs_bitset_init( - ecs_bitset_t* bs) +ecs_iter_t ecs_filter_chain_iter( + const ecs_iter_t *chain_it, + const ecs_filter_t *filter) { - bs->size = 0; - bs->count = 0; - bs->data = NULL; -} + ecs_iter_t it = { + .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 + }; -void flecs_bitset_ensure( - ecs_bitset_t *bs, - int32_t count) -{ - if (count > bs->count) { - bs->count = count; - ensure(bs, count); - } -} + 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); -void flecs_bitset_fini( - ecs_bitset_t *bs) -{ - ecs_os_free(bs->data); - bs->data = NULL; - bs->count = 0; -} + iter->kind = EcsIterEvalChain; -void flecs_bitset_addn( - ecs_bitset_t *bs, - int32_t count) -{ - int32_t elem = bs->count += count; - ensure(bs, elem); + return it; } -void flecs_bitset_set( - ecs_bitset_t *bs, - int32_t elem, - bool value) +bool ecs_filter_next( + ecs_iter_t *it) { - 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; -} + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); -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))); + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); 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) +bool ecs_filter_next_instanced( + ecs_iter_t *it) { - 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; -} + 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); -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); + 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; - 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; -} + flecs_iter_validate(it); -/** - * @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. - */ + ecs_iter_t *chain_it = it->chain_it; + ecs_iter_kind_t kind = iter->kind; -#include + 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; + } -/** - * stm32tpl -- STM32 C++ Template Peripheral Library - * Visit https://github.com/antongus/stm32tpl for new versions - * - * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA - */ + 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); -#define MAX_PRECISION (10) -#define EXP_THRESHOLD (3) -#define INT64_MAX_F ((double)INT64_MAX) + 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; -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 -}; + /* 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; -static -char* flecs_strbuf_itoa( - char *buf, - int64_t v) -{ - char *ptr = buf; - char * p1; - char c; + /* 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); - if (!v) { - *ptr++ = '0'; - } else { - if (v < 0) { - ptr[0] = '-'; - ptr ++; - v *= -1; + /* Can't set variable for filter that does not iterate tables */ + ecs_assert(kind == EcsIterEvalTables, + ECS_INVALID_OPERATION, NULL); + } } - char *p = ptr; - while (v) { - int64_t vdiv = v / 10; - int64_t vmod = v - (vdiv * 10); - p[0] = (char)('0' + vmod); - p ++; - v = vdiv; - } + 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; - p1 = p; + 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; + } - while (p > ptr) { - c = *--p; - *p = *ptr; - *ptr++ = c; - } - ptr = p1; - } - return ptr; -} + /* 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; + } -static -int 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; + it->offset = this_var->range.offset; + it->count = this_var->range.count; - if (isnan(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendlit(out, "NaN"); - return ecs_strbuf_appendch(out, nan_delim); - } else { - return ecs_strbuf_appendlit(out, "NaN"); - } - } - if (isinf(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendlit(out, "Inf"); - return ecs_strbuf_appendch(out, nan_delim); - } else { - return ecs_strbuf_appendlit(out, "Inf"); - } - } + /* But if it does, forward it to filter matching */ + ecs_assert(term_iter->table == this_table, + ECS_INTERNAL_ERROR, NULL); - if (precision > MAX_PRECISION) { - precision = MAX_PRECISION; - } + /* If This variable is not constrained, iterate as usual */ + } else { + it->offset = 0; + it->count = 0; - if (f < 0) { - f = -f; - *ptr++ = '-'; - } + /* 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; + } + } - 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; - } + ecs_assert(term_iter->match_count != 0, + ECS_INTERNAL_ERROR, NULL); - if (precision) { - f += rounders[precision]; - } + 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; + } - /* Make sure that number can be represented as 64bit int, increase exp */ - while (f > INT64_MAX_F) { - f /= 1000 * 1000 * 1000; - exp += 9; - } + iter->matches_left = term_iter->match_count; - intPart = (int64_t)f; - f -= (double)intPart; + /* Filter iterator takes control over iterating all the + * permutations that match the wildcard. */ + term_iter->match_count = 1; - ptr = flecs_strbuf_itoa(ptr, intPart); + table = term_iter->table; - if (precision) { - *ptr++ = '.'; - while (precision--) { - f *= 10.0; - c = (char)f; - *ptr++ = (char)('0' + c); - f -= c; - } - } - *ptr = 0; + 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; + } + } - /* Remove trailing 0s */ - while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { - ptr[-1] = '\0'; - ptr --; - } - if (ptr != buf && ptr[-1] == '.') { - ptr[-1] = '\0'; - ptr --; - } + /* 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; + } - /* 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 --; - } + /* 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; + } - if (exp || ((ptr - cur) > EXP_THRESHOLD)) { - cur[0] = '\0'; - exp += (ptr - cur); - ptr = cur; - } + ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); + } - if (exp) { - char *p1 = &buf[1]; - if (nan_delim) { - ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); - buf[0] = nan_delim; - p1 ++; - } + /* 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; - /* Make sure that exp starts after first character */ - c = p1[0]; + /* 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 (c) { - p1[0] = '.'; - do { - char t = (++p1)[0]; - if (t == '.') { - exp ++; - p1 --; - 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; } - p1[0] = c; - c = t; - exp ++; - } while (c); - ptr = p1 + 1; - } else { - ptr = p1; - } + 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); - ptr[0] = 'e'; - ptr = flecs_strbuf_itoa(ptr + 1, exp); + /* 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); + } + } - if (nan_delim) { - ptr[0] = nan_delim; - ptr ++; - } + match = iter->matches_left != 0; + iter->matches_left --; - ptr[0] = '\0'; + ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); + } while (!match); + + goto yield; } - - return 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) -{ - /* Allocate new element */ - ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded); - b->size += b->current->pos; - b->current->next = (ecs_strbuf_element*)e; - b->current = (ecs_strbuf_element*)e; - b->elementCount ++; - e->super.buffer_embedded = true; - e->super.buf = e->buf; - e->super.pos = 0; - e->super.next = NULL; -} +done: +error: + ecs_iter_fini(it); + return false; -/* Add an extra dynamic element */ -static -void flecs_strbuf_grow_str( - ecs_strbuf_t *b, - char *str, - char *alloc_str, - int32_t size) -{ - /* Allocate new element */ - ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str); - b->size += b->current->pos; - b->current->next = (ecs_strbuf_element*)e; - b->current = (ecs_strbuf_element*)e; - b->elementCount ++; - e->super.buffer_embedded = false; - e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); - e->super.next = NULL; - e->super.buf = str; - e->alloc_str = alloc_str; +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; } -static -char* flecs_strbuf_ptr( - ecs_strbuf_t *b) -{ - if (b->buf) { - return &b->buf[b->current->pos]; - } else { - return &b->current->buf[b->current->pos]; +/** + * @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;\ } -} -/* Compute the amount of space left in the current element */ -static -int32_t flecs_strbuf_memLeftInCurrentElement( - ecs_strbuf_t *b) -{ - if (b->current->buffer_embedded) { - return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; - } else { - return 0; +/* 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);\ } -} -/* Compute the amount of space left */ -static -int32_t flecs_strbuf_memLeft( - ecs_strbuf_t *b) +void* flecs_iter_calloc( + ecs_iter_t *it, + ecs_size_t size, + ecs_size_t align) { - if (b->max) { - return b->max - b->size - b->current->pos; - } else { - return INT_MAX; - } + 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); } -static -void flecs_strbuf_init( - ecs_strbuf_t *b) +void flecs_iter_free( + void *ptr, + ecs_size_t size) { - /* Initialize buffer structure only once */ - if (!b->elementCount) { - b->size = 0; - b->firstElement.super.next = NULL; - b->firstElement.super.pos = 0; - b->firstElement.super.buffer_embedded = true; - b->firstElement.super.buf = b->firstElement.buf; - b->elementCount ++; - b->current = (ecs_strbuf_element*)&b->firstElement; - } + flecs_stack_free(ptr, size); } -/* Append a format string to a buffer */ -static -bool flecs_strbuf_vappend( - ecs_strbuf_t *b, - const char* str, - va_list args) +void flecs_iter_init( + const ecs_world_t *world, + ecs_iter_t *it, + ecs_flags8_t fields) { - bool result = true; - va_list arg_cpy; - - if (!str) { - return result; - } - - flecs_strbuf_init(b); - - int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = flecs_strbuf_memLeft(b); + ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INTERNAL_ERROR, NULL); - if (!memLeft) { - return false; - } + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + ecs_stack_t *stack = &stage->allocators.iter_stack; - /* Compute the memory required to add the string to the buffer. If user - * provided buffer, use space left in buffer, otherwise use space left in - * current element. */ - int32_t max_copy = b->buf ? memLeft : memLeftInElement; - int32_t memRequired; + 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); - va_copy(arg_cpy, args); - memRequired = vsnprintf( - flecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); + 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); +} - ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); +void flecs_iter_validate( + ecs_iter_t *it) +{ + ECS_BIT_SET(it->flags, EcsIterIsValid); - if (memRequired <= memLeftInElement) { - /* Element was large enough to fit string */ - b->current->pos += memRequired; - } else if ((memRequired - memLeftInElement) < memLeft) { - /* If string is a format string, a new buffer of size memRequired is - * needed to re-evaluate the format string and only use the part that - * wasn't already copied to the previous element */ - if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { - /* Resulting string fits in standard-size buffer. Note that the - * entire string needs to fit, not just the remainder, as the - * format string cannot be partially evaluated */ - flecs_strbuf_grow(b); + /* 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; +} - /* Copy entire string to new buffer */ - ecs_os_vsprintf(flecs_strbuf_ptr(b), str, arg_cpy); +void ecs_iter_fini( + ecs_iter_t *it) +{ + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - /* Ignore the part of the string that was copied into the - * previous buffer. The string copied into the new buffer could - * be memmoved so that only the remainder is left, but that is - * most likely more expensive than just keeping the entire - * string. */ + if (it->fini) { + it->fini(it); + } - /* Update position in buffer */ - b->current->pos += memRequired; - } else { - /* Resulting string does not fit in standard-size buffer. - * Allocate a new buffer that can hold the entire string. */ - char *dst = ecs_os_malloc(memRequired + 1); - ecs_os_vsprintf(dst, str, arg_cpy); - flecs_strbuf_grow_str(b, dst, dst, memRequired); - } + ecs_world_t *world = it->world; + if (!world) { + return; } - va_end(arg_cpy); + 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); - return flecs_strbuf_memLeft(b) > 0; + 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_strbuf_appendstr( - ecs_strbuf_t *b, - const char* str, - int n) +bool flecs_iter_populate_term_data( + ecs_world_t *world, + ecs_iter_t *it, + int32_t t, + int32_t column, + void **ptr_out) { - flecs_strbuf_init(b); + bool is_shared = false; + ecs_table_t *table; + void *data; + int32_t row, u_index; - int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = flecs_strbuf_memLeft(b); - if (memLeft <= 0) { - return false; + if (!column) { + /* Term has no data. This includes terms that have Not operators. */ + goto no_data; } - /* Never write more than what the buffer can store */ - if (n > memLeft) { - n = memLeft; + /* Filter terms may match with data but don't return it */ + if (it->terms[t].inout == EcsInOutNone) { + goto no_data; } - if (n <= memLeftInElement) { - /* Element was large enough to fit string */ - ecs_os_strncpy(flecs_strbuf_ptr(b), str, n); - b->current->pos += n; - } else if ((n - memLeftInElement) < memLeft) { - ecs_os_strncpy(flecs_strbuf_ptr(b), str, memLeftInElement); + ecs_assert(it->sizes != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t size = it->sizes[t]; + if (!size) { + goto no_data; + } - /* Element was not large enough, but buffer still has space */ - b->current->pos += memLeftInElement; - n -= memLeftInElement; + if (column < 0) { + table = it->table; + is_shared = true; - /* Current element was too small, copy remainder into new element */ - if (n < ECS_STRBUF_ELEMENT_SIZE) { - /* A standard-size buffer is large enough for the new string */ - flecs_strbuf_grow(b); + /* 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])) { - /* Copy the remainder to the new buffer */ - if (n) { - /* If a max number of characters to write is set, only a - * subset of the string should be copied to the buffer */ - ecs_os_strncpy( - flecs_strbuf_ptr(b), - str + memLeftInElement, - (size_t)n); - } else { - ecs_os_strcpy(flecs_strbuf_ptr(b), str + memLeftInElement); + /* 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; } - /* Update to number of characters copied to new buffer */ - b->current->pos += n; + return true; } else { - /* String doesn't fit in a single element, strdup */ - char *remainder = ecs_os_strdup(str + memLeftInElement); - flecs_strbuf_grow_str(b, remainder, remainder, n); - } - } else { - /* Buffer max has been reached */ - return false; - } + ecs_entity_t subj = it->sources[t]; + ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); - return flecs_strbuf_memLeft(b) > 0; -} + /* 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); -static -bool flecs_strbuf_appendch( - ecs_strbuf_t *b, - char ch) -{ - flecs_strbuf_init(b); + row = ECS_RECORD_TO_ROW(r->row); + table = r->table; - int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = flecs_strbuf_memLeft(b); - if (memLeft <= 0) { - return false; - } + ecs_id_t id = it->ids[t]; + ecs_table_record_t *tr; - if (memLeftInElement) { - /* Element was large enough to fit string */ - flecs_strbuf_ptr(b)[0] = ch; - b->current->pos ++; + 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 { - flecs_strbuf_grow(b); - flecs_strbuf_ptr(b)[0] = ch; - b->current->pos ++; - } + /* Data is from This, use table from iterator */ + table = it->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return flecs_strbuf_memLeft(b) > 0; -} + row = it->offset; -bool 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); - return flecs_strbuf_vappend(b, fmt, args); -} - -bool 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); - bool result = flecs_strbuf_vappend(b, fmt, args); - va_end(args); - - return result; -} - -bool 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); - return flecs_strbuf_appendstr(b, str, len); -} - -bool ecs_strbuf_appendch( - ecs_strbuf_t *b, - char ch) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_strbuf_appendch(b, ch); -} + 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; + } -bool 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); - return ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); -} + if (!it->count) { + goto no_data; + } -bool ecs_strbuf_appendflt( - ecs_strbuf_t *b, - double flt, - char nan_delim) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_strbuf_ftoa(b, flt, 10, nan_delim); -} + ecs_vec_t *s = &table->data.columns[storage_column].data; + data = ecs_vec_first(s); -bool ecs_strbuf_appendstr_zerocpy( - ecs_strbuf_t *b, - char* str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_strbuf_init(b); - flecs_strbuf_grow_str(b, str, str, 0); - return true; -} + /* Fallthrough to has_data */ + } -bool ecs_strbuf_appendstr_zerocpyn( - ecs_strbuf_t *b, - char *str, - int32_t n) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_strbuf_init(b); - flecs_strbuf_grow_str(b, str, str, n); - return true; -} +has_data: + if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); + return is_shared; -bool ecs_strbuf_appendstr_zerocpy_const( - ecs_strbuf_t *b, - const char* str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - /* Removes const modifier, but logic prevents changing / delete string */ - flecs_strbuf_init(b); - flecs_strbuf_grow_str(b, (char*)str, NULL, 0); - return true; -} +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; + } -bool ecs_strbuf_appendstr_zerocpyn_const( - 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); - /* Removes const modifier, but logic prevents changing / delete string */ - flecs_strbuf_init(b); - flecs_strbuf_grow_str(b, (char*)str, NULL, n); - return true; +no_data: + if (ptr_out) ptr_out[0] = NULL; + return false; } -bool ecs_strbuf_appendstr( - ecs_strbuf_t *b, - const char* str) +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_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); -} + ecs_table_t *prev_table = it->table; + if (prev_table) { + it->frame_offset += ecs_table_count(prev_table); + } -bool ecs_strbuf_mergebuff( - ecs_strbuf_t *dst_buffer, - ecs_strbuf_t *src_buffer) -{ - if (src_buffer->elementCount) { - if (src_buffer->buf) { - return ecs_strbuf_appendstrn( - dst_buffer, src_buffer->buf, src_buffer->length); + 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 { - ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; + it->entities = NULL; + } + } - /* Copy first element as it is inlined in the src buffer */ - ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); + int t, field_count = it->field_count; + if (ECS_BIT_IS_SET(it->flags, EcsIterNoData)) { + ECS_BIT_CLEAR(it->flags, EcsIterHasShared); + return; + } - while ((e = e->next)) { - dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); - *dst_buffer->current->next = *e; - } + 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]); } - - *src_buffer = ECS_STRBUF_INIT; } - return true; + ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); } -char* ecs_strbuf_get( - ecs_strbuf_t *b) +bool flecs_iter_next_row( + ecs_iter_t *it) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - char* result = NULL; - if (b->elementCount) { - if (b->buf) { - b->buf[b->current->pos] = '\0'; - result = ecs_os_strdup(b->buf); - } else { - void *next = NULL; - int32_t len = b->size + b->current->pos + 1; - ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + 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; - result = ecs_os_malloc(len); - char* ptr = result; + if (instance_count > count && offset < (instance_count - 1)) { + ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); + int t, field_count = it->field_count; - do { - ecs_os_memcpy(ptr, e->buf, e->pos); - ptr += e->pos; - next = e->next; - if (e != &b->firstElement.super) { - if (!e->buffer_embedded) { - ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); + for (t = 0; t < field_count; t ++) { + int32_t column = it->columns[t]; + if (column >= 0) { + void *ptr = it->ptrs[t]; + if (ptr) { + it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); } - ecs_os_free(e); } - } while ((e = next)); + } - result[len - 1] = '\0'; - b->length = len; + if (it->entities) { + it->entities ++; + } + it->offset ++; + + return true; } - } else { - result = NULL; } - b->elementCount = 0; - - b->content = result; - - return result; + return false; } -char *ecs_strbuf_get_small( - ecs_strbuf_t *b) +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + 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; + } - int32_t written = ecs_strbuf_written(b); - ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL); - char *buf = b->firstElement.buf; - buf[written] = '\0'; - return buf; + return result; } -void ecs_strbuf_reset( - ecs_strbuf_t *b) +/* --- Public API --- */ + +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); - if (b->elementCount && !b->buf) { - void *next = NULL; - ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; - do { - next = e->next; - if (e != (ecs_strbuf_element*)&b->firstElement) { - ecs_os_free(e); - } - } while ((e = next)); - } + ecs_check(!size || ecs_field_size(it, index) == size || + (!ecs_field_size(it, index) && (!it->ptrs[index - 1])), + ECS_INVALID_PARAMETER, NULL); + (void)size; - *b = ECS_STRBUF_INIT; + return it->ptrs[index - 1]; +error: + return NULL; } -void ecs_strbuf_list_push( - ecs_strbuf_t *b, - const char *list_open, - const char *separator) +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int32_t index) { - 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); + 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]; - b->list_stack[b->list_sp].count = 0; - b->list_stack[b->list_sp].separator = separator; + if (term->inout == EcsIn) { + return true; + } else if (term->inout == EcsInOutDefault) { + if (!ecs_term_match_this(term)) { + return true; + } - 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); + ecs_term_id_t *src = &term->src; + if (!(src->flags & EcsSelf)) { + return true; } } +error: + return false; } -void ecs_strbuf_list_pop( - ecs_strbuf_t *b, - const char *list_close) +bool ecs_field_is_writeonly( + const ecs_iter_t *it, + int32_t index) { - 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); - } - } + 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; } -void ecs_strbuf_list_next( - ecs_strbuf_t *b) +bool ecs_field_is_set( + const ecs_iter_t *it, + int32_t index) { - 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]); + 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 { - ecs_strbuf_appendstr(b, sep); + return true; } } - b->list_stack[list_sp].count ++; -} - -bool ecs_strbuf_list_appendch( - ecs_strbuf_t *b, - char ch) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_list_next(b); - return flecs_strbuf_appendch(b, ch); + return true; +error: + return false; } -bool ecs_strbuf_list_append( - ecs_strbuf_t *b, - const char *fmt, - ...) +bool ecs_field_is_self( + const ecs_iter_t *it, + int32_t index) { - 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); - bool result = flecs_strbuf_vappend(b, fmt, args); - va_end(args); - - return result; + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + return it->sources == NULL || it->sources[index - 1] == 0; } -bool ecs_strbuf_list_appendstr( - ecs_strbuf_t *b, - const char *str) +ecs_id_t ecs_field_id( + const ecs_iter_t *it, + int32_t index) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_strbuf_list_next(b); - return ecs_strbuf_appendstr(b, str); + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + return it->ids[index - 1]; } -bool ecs_strbuf_list_appendstrn( - ecs_strbuf_t *b, - const char *str, - int32_t n) +int32_t ecs_field_column_index( + const ecs_iter_t *it, + int32_t index) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_strbuf_list_next(b); - return ecs_strbuf_appendstrn(b, str, n); + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + int32_t result = it->columns[index - 1]; + if (result <= 0) { + return -1; + } else { + return result - 1; + } } -int32_t ecs_strbuf_written( - const ecs_strbuf_t *b) +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int32_t index) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - if (b->current) { - return b->size + b->current->pos; + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + if (it->sources) { + return it->sources[index - 1]; } else { return 0; } } -/** - * @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) +size_t ecs_field_size( + const ecs_iter_t *it, + int32_t index) { - 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_DEBUG - v->elem_size = size; -#endif - return v; + ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); + return (size_t)it->sizes[index - 1]; } -void ecs_vec_init_if( - ecs_vec_t *vec, - ecs_size_t size) +char* ecs_iter_str( + const ecs_iter_t *it) { - ecs_dbg_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); - (void)vec; - (void)size; -#ifdef FLECS_DEBUG - 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; + if (!(it->flags & EcsIterIsValid)) { + return NULL; } -#endif -} -void ecs_vec_fini( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size) -{ - if (v->array) { - ecs_dbg_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); + 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); } - v->array = NULL; - v->count = 0; - v->size = 0; - } -} + ecs_strbuf_list_pop(&buf, "\n"); -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_dbg_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, - ecs_vec_t *v, - ecs_size_t size) -{ - ecs_dbg_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_DEBUG - , .elem_size = size -#endif - }; -} + 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"); -void ecs_vec_reclaim( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size) -{ - ecs_dbg_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); + 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 { - v->array = ecs_os_realloc(v->array, size * count); + ecs_strbuf_list_appendlit(&buf, "false"); } - v->size = count; - } else { - ecs_vec_fini(allocator, v, size); } + ecs_strbuf_list_pop(&buf, "\n"); } -} -void ecs_vec_set_size( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size, - int32_t elem_count) -{ - ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); - if (v->size != elem_count) { - if (elem_count < v->count) { - elem_count = v->count; - } + 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; + } - 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); + ecs_var_t var = it->variables[i]; + if (!var.entity) { + /* Skip table variables */ + continue; } - v->size = elem_count; + + 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"); } } -} -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); + 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_vec_set_min_count( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size, - int32_t elem_count) +void ecs_iter_poly( + const ecs_world_t *world, + const ecs_poly_t *poly, + ecs_iter_t *iter_out, + ecs_term_t *filter) { - ecs_vec_set_min_size(allocator, vec, size, elem_count); - if (vec->count < elem_count) { - vec->count = elem_count; - } + ecs_iterable_t *iterable = ecs_get_iterable(poly); + iterable->init(world, poly, iter_out, filter); } -void ecs_vec_set_min_count_zeromem( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size, - int32_t elem_count) +bool ecs_iter_next( + ecs_iter_t *iter) { - 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)); - } + ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); + return iter->next(iter); +error: + return false; } -void ecs_vec_set_count( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size, - int32_t elem_count) +int32_t ecs_iter_count( + ecs_iter_t *it) { - ecs_dbg_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); - } + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - v->count = elem_count; + 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; } -void* ecs_vec_grow( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size, - int32_t elem_count) +ecs_entity_t ecs_iter_first( + ecs_iter_t *it) { - ecs_dbg_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); + 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; } -void* ecs_vec_append( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size) +bool ecs_iter_is_true( + ecs_iter_t *it) { - ecs_dbg_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); + 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); } - v->count = count + 1; - return ECS_ELEM(v->array, size, count); + return result; +error: + return false; } -void ecs_vec_remove( - ecs_vec_t *v, - ecs_size_t size, - int32_t index) +ecs_entity_t ecs_iter_get_var( + ecs_iter_t *it, + int32_t var_id) { - ecs_dbg_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_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) { + 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); } - ecs_os_memcpy( - ECS_ELEM(v->array, size, index), - ECS_ELEM(v->array, size, v->count), - size); + return e; +error: + return 0; } -void ecs_vec_remove_last( - ecs_vec_t *v) +ecs_table_t* ecs_iter_get_var_as_table( + ecs_iter_t *it, + int32_t var_id) { - v->count --; -} + 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); -int32_t ecs_vec_count( - const ecs_vec_t *v) -{ - return v->count; + ecs_var_t *var = &it->variables[var_id]; + ecs_table_t *table = var->range.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; } -int32_t ecs_vec_size( - const ecs_vec_t *v) +ecs_table_range_t ecs_iter_get_var_as_range( + ecs_iter_t *it, + int32_t var_id) { - return v->size; + 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) { + 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_vec_get( - const ecs_vec_t *v, - ecs_size_t size, - int32_t index) +void ecs_iter_set_var( + ecs_iter_t *it, + int32_t var_id, + ecs_entity_t entity) { - ecs_dbg_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); + 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_vec_last( - const ecs_vec_t *v, - ecs_size_t size) +void ecs_iter_set_var_as_table( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_t *table) { - ecs_dbg_assert(!v->elem_size || size == v->elem_size, - ECS_INVALID_PARAMETER, NULL); - return ECS_ELEM(v->array, size, v->count - 1); + 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_vec_first( - const ecs_vec_t *v) +void ecs_iter_set_var_as_range( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_range_t *range) { - return v->array; -} + 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); -/** - * @file datastructures/map.c - * @brief Map data structure. - * - * Map data structure for 64bit keys and dynamic payload size. - */ + /* 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; -/* 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) + 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; + } -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}; + it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; +error: + return; } -/* Get bucket count for number of elements */ -static -int32_t flecs_map_get_bucket_count( - int32_t count) +bool ecs_iter_var_is_constrained( + ecs_iter_t *it, + int32_t var_id) { - return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); + return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; } -/* Get bucket shift amount for a given bucket count */ static -uint8_t flecs_map_get_bucket_shift ( - int32_t bucket_count) +void ecs_chained_iter_fini( + ecs_iter_t *it) { - return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); + 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; } -/* 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_iter_t ecs_page_iter( + const ecs_iter_t *it, + int32_t offset, + int32_t limit) { - ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); - return (int32_t)((11400714819323198485ull * key) >> bucket_shift); + 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 }; } -/* Get bucket for key */ static -ecs_bucket_t* flecs_map_get_bucket( - const ecs_map_t *map, - ecs_map_key_t key) +void flecs_offset_iter( + ecs_iter_t *it, + int32_t offset) { - 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]; -} + it->entities = &it->entities[offset]; -/* 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; -} + 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; + } -/* 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; + if (it->sources[t]) { + continue; } - *next_holder = entry->next; - flecs_bfree(map->entry_allocator, entry); - map->count --; - return value; + + it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); } } - - return 0; } -/* Free contents of bucket */ static -void flecs_map_bucket_clear( - ecs_block_allocator_t *allocator, - ecs_bucket_t *bucket) +bool ecs_page_next_instanced( + ecs_iter_t *it) { - ecs_bucket_entry_t *entry = bucket->first; - while(entry) { - ecs_bucket_entry_t *next = entry->next; - flecs_bfree(allocator, entry); - entry = next; - } -} + 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); -/* 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; + 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; } - } - 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); + 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)); - 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); + /* Keep instancing setting from original iterator */ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); - /* 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 (!chain_it->table) { + goto yield; /* Task query */ } - } - - 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); -} + int32_t offset = iter->offset; + int32_t limit = iter->limit; + if (!(offset || limit)) { + if (it->count) { + goto yield; + } else { + goto depleted; + } + } -void ecs_map_params_fini( - ecs_map_params_t *params) -{ - flecs_ballocator_fini(¶ms->entry_allocator); -} + int32_t count = it->count; + int32_t remaining = iter->remaining; -void ecs_map_init_w_params( - ecs_map_t *result, - ecs_map_params_t *params) -{ - ecs_os_zeromem(result); + 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); + } + } - result->allocator = params->allocator; + 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); - 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); +yield: + if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { + it->offset = 0; } - flecs_map_rehash(result, 0); + return true; +done: + /* Cleanup iterator resources if it wasn't yet depleted */ + ecs_iter_fini(chain_it); +depleted: +error: + return false; } -void ecs_map_init_w_params_if( - ecs_map_t *result, - ecs_map_params_t *params) +bool ecs_page_next( + ecs_iter_t *it) { - if (!ecs_map_is_init(result)) { - ecs_map_init_w_params(result, params); + 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; } -} -void ecs_map_init( - ecs_map_t *result, - ecs_allocator_t *allocator) -{ - ecs_map_init_w_params(result, &(ecs_map_params_t) { - .allocator = allocator - }); + return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); +error: + return false; } -void ecs_map_init_if( - ecs_map_t *result, - ecs_allocator_t *allocator) +ecs_iter_t ecs_worker_iter( + const ecs_iter_t *it, + int32_t index, + int32_t count) { - if (!ecs_map_is_init(result)) { - ecs_map_init(result, allocator); - } + 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 }; } -void ecs_map_fini( - ecs_map_t *map) +static +bool ecs_worker_next_instanced( + ecs_iter_t *it) { - if (!ecs_map_is_init(map)) { - return; - } + 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 sanitize = false; -#ifdef FLECS_SANITIZE - sanitize = true; -#endif + bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - /* 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 ++; + 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; } - } - 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); + /* 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 { - ecs_os_free(map->buckets); + it->offset = 0; } - map->bucket_shift = 0; + return true; +error: + return false; } -ecs_map_val_t* ecs_map_get( - const ecs_map_t *map, - ecs_map_key_t key) +bool ecs_worker_next( + ecs_iter_t *it) { - return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); + 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; } -void* _ecs_map_get_deref( - const ecs_map_t *map, - ecs_map_key_t key) +/** + * @file misc.c + * @brief Miscallaneous 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) { - ecs_map_val_t* ptr = flecs_map_bucket_get( - flecs_map_get_bucket(map, key), key); - if (ptr) { - return (void*)ptr[0]; + 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 NULL; + + return u; } +#endif -void ecs_map_insert( - ecs_map_t *map, - ecs_map_key_t key, - ecs_map_val_t value) +int32_t flecs_next_pow_of_2( + int32_t n) { - 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); - } + n --; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n ++; - ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); - flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; + return n; } -void* ecs_map_insert_alloc( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) +/** Convert time to double */ +double ecs_time_to_double( + ecs_time_t t) { - void *elem = ecs_os_calloc(elem_size); - ecs_map_insert_ptr(map, key, elem); - return elem; + double result; + result = t.sec; + return result + (double)t.nanosec / (double)1000000000; } -ecs_map_val_t* ecs_map_ensure( - ecs_map_t *map, - ecs_map_key_t key) +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2) { - 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; - } + ecs_time_t 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); + 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; } - ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); - *v = 0; - return v; + return result; } -void* ecs_map_ensure_alloc( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) +void ecs_sleepf( + double t) { - ecs_map_val_t *val = ecs_map_ensure(map, key); - if (!*val) { - void *elem = ecs_os_calloc(elem_size); - *val = (ecs_map_val_t)elem; - return elem; - } else { - return (void*)*val; + if (t > 0) { + int sec = (int)t; + int nsec = (int)((t - sec) * 1000000000); + ecs_os_sleep(sec, nsec); } } -ecs_map_val_t ecs_map_remove( - ecs_map_t *map, - ecs_map_key_t key) +double ecs_time_measure( + ecs_time_t *start) { - return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); + 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_map_remove_free( - ecs_map_t *map, - ecs_map_key_t key) +void* ecs_os_memdup( + const void *src, + ecs_size_t size) { - ecs_map_val_t val = ecs_map_remove(map, key); - if (val) { - ecs_os_free((void*)val); + 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; } -void ecs_map_clear( - ecs_map_t *map) +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) { - 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); + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); } -ecs_map_iter_t ecs_map_iter( - const ecs_map_t *map) +uint64_t flecs_string_hash( + const void *ptr) { - if (ecs_map_is_init(map)) { - return (ecs_map_iter_t){ - .map = map, - .bucket = NULL, - .entry = NULL - }; - } else { - return (ecs_map_iter_t){ 0 }; - } + const ecs_hashed_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; } -bool ecs_map_next( - ecs_map_iter_t *iter) +char* ecs_vasprintf( + const char *fmt, + va_list args) { - const ecs_map_t *map = iter->map; - ecs_bucket_t *end; - if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { - return false; - } + ecs_size_t size = 0; + char *result = NULL; + va_list tmpa; - 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; - } + va_copy(tmpa, args); - ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); - iter->entry = entry->next; - iter->res = &entry->key; + size = vsnprintf(result, 0, fmt, tmpa); - return true; -} + va_end(tmpa); -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; + if ((int32_t)size < 0) { + return NULL; } - ecs_map_init(dst, src->allocator); + result = (char *) ecs_os_malloc(size + 1); - 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)); + if (!result) { + return NULL; } -} - -/** - * @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 + ecs_os_vsprintf(result, fmt, args); -int64_t ecs_block_allocator_alloc_count = 0; -int64_t ecs_block_allocator_free_count = 0; + return result; +} -static -ecs_block_allocator_chunk_header_t* flecs_balloc_block( - ecs_block_allocator_t *allocator) +char* ecs_asprintf( + const char *fmt, + ...) { - if (!allocator->chunk_size) { - return NULL; - } + va_list args; + va_start(args, fmt); + char *result = ecs_vasprintf(fmt, args); + va_end(args); + return result; +} - 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)); +char* flecs_to_snake_case(const char *str) { + int32_t upper_count = 0, len = 1; + const char *ptr = str; + char ch, *out, *out_ptr; - 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; + for (ptr = &str[1]; (ch = *ptr); ptr ++) { + if (isupper(ch)) { + upper_count ++; + } + len ++; } - 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; + 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 ++; + } } - ecs_os_linc(&ecs_block_allocator_alloc_count); + out_ptr[0] = '\0'; - chunk->next = NULL; - return first_chunk; + return out; } -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; -} +/** + * @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. + */ -ecs_block_allocator_t* flecs_ballocator_new( - ecs_size_t size) + +void flecs_observable_init( + ecs_observable_t *observable) { - ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); - flecs_ballocator_init(result, size); - return result; + 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_ballocator_fini( - ecs_block_allocator_t *ba) +void flecs_observable_fini( + ecs_observable_t *observable) { - ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + 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); -#ifdef FLECS_SANITIZE - ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, NULL); -#endif + 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; - 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; + /* All triggers should've unregistered by now */ + ecs_assert(!ecs_map_is_init(&er->event_ids), + ECS_INTERNAL_ERROR, NULL); } - ba->block_head = NULL; -} -void flecs_ballocator_free( - ecs_block_allocator_t *ba) -{ - flecs_ballocator_fini(ba); - ecs_os_free(ba); + flecs_sparse_fini(&observable->events); } -void* flecs_balloc( - ecs_block_allocator_t *ba) +ecs_event_record_t* flecs_event_record_get( + const ecs_observable_t *o, + ecs_entity_t event) { - 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 + 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); - return result; + /* User events */ + return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); } -void* flecs_bcalloc( - ecs_block_allocator_t *ba) +ecs_event_record_t* flecs_event_record_ensure( + ecs_observable_t *o, + ecs_entity_t event) { -#ifdef FLECS_USE_OS_ALLOC - return ecs_os_calloc(ba->data_size); -#endif + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - if (!ba) return NULL; - void *result = flecs_balloc(ba); - ecs_os_memset(result, 0, ba->data_size); - return result; + 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; } -void flecs_bfree( - ecs_block_allocator_t *ba, - void *memory) +static +const ecs_event_record_t* flecs_event_record_get_if( + const ecs_observable_t *o, + ecs_entity_t event) { -#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; - } + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); -#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); + 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; + } } - 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"); + return NULL; } -void* flecs_brealloc( - ecs_block_allocator_t *dst, - ecs_block_allocator_t *src, - void *memory) +ecs_event_id_record_t* flecs_event_id_record_get( + const ecs_event_record_t *er, + ecs_id_t id) { - void *result; -#ifdef FLECS_USE_OS_ALLOC - result = ecs_os_realloc(memory, dst->data_size); -#else - if (dst == src) { - return memory; + if (!er) { + return NULL; } - result = flecs_balloc(dst); - if (result && src) { - ecs_size_t size = src->data_size; - if (dst->data_size < size) { - size = dst->data_size; + 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); } - 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); + return NULL; } -#endif - - return result; } -void* flecs_bdup( - ecs_block_allocator_t *ba, - void *memory) +static +ecs_event_id_record_t* flecs_event_id_record_get_if( + const ecs_event_record_t *er, + ecs_id_t id) { -#ifdef FLECS_USE_OS_ALLOC - if (memory && ba->chunk_size) { - return ecs_os_memdup(memory, ba->data_size); - } else { + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (!ider) { return NULL; } -#endif - void *result = flecs_balloc(ba); - if (result) { - ecs_os_memcpy(result, memory, ba->data_size); + if (ider->observer_count) { + return ider; } - return result; + + return NULL; } -/** - * @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. - */ +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); -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; - } + 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; } - 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); + 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_hashmap_fini( - ecs_hashmap_t *map) +void flecs_event_id_record_remove( + ecs_event_record_t *er, + ecs_id_t id) { - 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 + 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); + } } - - flecs_ballocator_fini(&map->bucket_allocator); - ecs_map_fini(&map->impl); } -void flecs_hashmap_copy( - ecs_hashmap_t *dst, - const ecs_hashmap_t *src) +static +int32_t flecs_event_observers_get( + const ecs_event_record_t *er, + ecs_id_t id, + ecs_event_id_record_t **iders) { - 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; + if (!er) { + return 0; } - 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); + /* Populate array with observer sets matching the id */ + int32_t count = 0; + iders[0] = flecs_event_id_record_get_if(er, EcsAny); + count += iders[count] != 0; - 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); - } + iders[count] = flecs_event_id_record_get_if(er, id); + count += iders[count] != 0; - 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); + 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 { - 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); - } + iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); + count += iders[count] != 0; } - return (flecs_hashmap_result_t){ - .key = key_ptr, .value = value_ptr, .hash = hash - }; + return count; } -void _flecs_hashmap_set( - ecs_hashmap_t *map, - ecs_size_t key_size, - void *key, - ecs_size_t value_size, - const void *value) +bool flecs_observers_exist( + ecs_observable_t *observable, + ecs_id_t id, + ecs_entity_t event) { - 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); -} + const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + if (!er) { + return false; + } -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); + return flecs_event_id_record_get_if(er, id) != NULL; } -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); - } -} +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); -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) +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_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) { + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { return; } - int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); - if (index == -1) { - return; - } + 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 (!ecs_table_count(table)) { + continue; + } - flecs_hm_bucket_remove(map, bucket, hash, index); -} + bool owned = flecs_id_record_get_table(idr, table) != NULL; -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); + 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); + } - uint64_t hash = map->hash(key); - _flecs_hashmap_remove_w_hash(map, key_size, key, value_size, hash); -} + /* Treat as new event as this could invoke observers again for + * different tables. */ + int32_t evtx = ++ world->event_id; -flecs_hashmap_iter_t flecs_hashmap_iter( - ecs_hashmap_t *map) -{ - return (flecs_hashmap_iter_t){ - .it = ecs_map_iter(&map->impl) - }; -} + 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, evtx); -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; + if (!owned) { + /* Owned takes precedence */ + flecs_observers_invoke( + world, &ider->self_up, it, table, trav, evtx); + } } - index = it->index = 0; - } - if (key_out) { - *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); + if (!table->_->traversable_count) { + continue; + } + + ecs_record_t **records = ecs_vec_first(&table->data.records); + for (e = 0; e < entity_count; e ++) { + ecs_record_t *r = records[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); + } + } } - - return ecs_vec_get(&bucket->values, value_size, index); } -/** - * @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) +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_stack_page_t *page = stack->cur; - if (page == &stack->first && !page->data) { - page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); - ecs_os_linc(&ecs_stack_allocator_alloc_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(); - 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; + /* Propagate to records of traversable relationships */ + ecs_id_record_t *cur = tgt_idr; + while ((cur = cur->trav.next)) { + cur->reachable.generation ++; /* Invalidate cache */ - if (next_sp > ECS_STACK_PAGE_SIZE) { - if (size > ECS_STACK_PAGE_SIZE) { - result = ecs_os_malloc(size); /* Too large for page */ - goto done; + /* Get traversed relationship */ + ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); + if (propagate_trav && propagate_trav != trav) { + continue; } - 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->cur = page; + flecs_emit_propagate_id( + world, it, idr, cur, trav, iders, ider_count); } - page->sp = next_sp; - result = ECS_OFFSET(page->data, sp); - -done: -#ifdef FLECS_SANITIZE - ecs_os_memset(result, 0xAA, size); -#endif - return result; + ecs_log_pop_3(); } -void* flecs_stack_calloc( - ecs_stack_t *stack, - ecs_size_t size, - ecs_size_t align) +static +void flecs_emit_propagate_invalidate_tables( + ecs_world_t *world, + ecs_id_record_t *tgt_idr) { - void *ptr = flecs_stack_alloc(stack, size, align); - ecs_os_memset(ptr, 0, size); - return ptr; -} + ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_stack_free( - void *ptr, - ecs_size_t size) -{ - if (size > ECS_STACK_PAGE_SIZE) { - ecs_os_free(ptr); + 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); } -} -ecs_stack_cursor_t flecs_stack_get_cursor( - ecs_stack_t *stack) -{ - return (ecs_stack_cursor_t){ - .cur = stack->cur, .sp = stack->cur->sp - }; -} + /* 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; + } -void flecs_stack_restore_cursor( - ecs_stack_t *stack, - const ecs_stack_cursor_t *cursor) -{ - ecs_stack_page_t *cur = cursor->cur; - if (!cur) { - return; - } + rc->generation ++; - if (cur == stack->cur) { - if (cursor->sp > stack->cur->sp) { - return; + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + continue; } - } else if (cur->id > stack->cur->id) { - return; - } - - stack->cur = cursor->cur; - stack->cur->sp = cursor->sp; -} -void flecs_stack_reset( - ecs_stack_t *stack) -{ - stack->cur = &stack->first; - stack->first.sp = 0; -} + 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; + } -void flecs_stack_init( - ecs_stack_t *stack) -{ - ecs_os_zeromem(stack); - stack->cur = &stack->first; - stack->first.data = NULL; -} + int32_t e, entity_count = ecs_table_count(table); + ecs_record_t **records = ecs_vec_first(&table->data.records); -void flecs_stack_fini( - ecs_stack_t *stack) -{ - ecs_stack_page_t *next, *cur = &stack->first; - ecs_assert(stack->cur == &stack->first, ECS_LEAK_DETECTED, NULL); - ecs_assert(stack->cur->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); + for (e = 0; e < entity_count; e ++) { + ecs_id_record_t *idr_t = records[e]->idr; + if (idr_t) { + /* Only notify for entities that are used in pairs with + * traversable relationships */ + flecs_emit_propagate_invalidate_tables(world, idr_t); + } } - ecs_os_free(cur->data); - } else { - ecs_os_linc(&ecs_stack_allocator_free_count); - ecs_os_free(cur); } - } while ((cur = next)); + } } -/** - * @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( +void flecs_emit_propagate_invalidate( + ecs_world_t *world, ecs_table_t *table, - ecs_entity_filter_iter_t *iter) + int32_t offset, + int32_t count) { - 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; - + ecs_record_t **recs = ecs_vec_get_t(&table->data.records, + ecs_record_t*, offset); + int32_t i; 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]; + ecs_record_t *record = recs[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; } - /* 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; + 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); } } - - return index; } static -int flecs_entity_filter_switch_next( +void flecs_override_copy( + ecs_world_t *world, ecs_table_t *table, - ecs_entity_filter_iter_t *iter, - bool filter) + const ecs_type_info_t *ti, + void *dst, + const void *src, + int32_t offset, + int32_t count) { - 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); + 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 { - 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; - } + for (i = 0; i < count; i ++) { + ecs_os_memcpy(ptr, src, size); + ptr = ECS_OFFSET(ptr, size); } } - 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; - } + 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); + } +} - column = &columns[i]; - sw = column->sw_column; +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; + } - if (flecs_switch_get(sw, first) != column->sw_case) { - first = flecs_switch_next(sw_smallest, first); - if (first == -1) { - goto done; - } + 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; } - } - } while (i != count); - iter->range.offset = iter->sw_offset = first; - iter->range.count = 1; + int32_t index = tr->column; + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - 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; + 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 -1; + return NULL; } -#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) +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, + int32_t evtx); static -int flecs_entity_filter_bitset_next( +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_entity_filter_iter_t *iter) + ecs_id_record_t *idr, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + int32_t column, + int32_t offset, + ecs_entity_t trav, + int32_t evtx) { - /* 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) - }; + 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]; - 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; + /* 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); + } - for (i = 0; i < count; i ++) { - flecs_bitset_term_t *column = &terms[i]; - ecs_bitset_t *bs = terms[i].bs_column; + if (!may_override && (!ider_count && !ider_onset_count)) { + return; + } - 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; + it->ids[0] = id; + it->sources[0] = tgt; + it->event_id = id; + it->ptrs[0] = NULL; + it->sizes[0] = 0; - if (bs_block >= bs_block_count) { - goto done; - } + 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; + } - uint64_t *data = bs->data; - int32_t bs_start = first & 0x3F; + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + bool owned = tr != NULL; - /* 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; - } + 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, evtx); - bs_start = 0; - remain = BS_MAX; /* Test the full block */ - v = data[bs_block]; + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke(world, &ider->self_up, it, table, trav, evtx); } + } - /* Step 2: find the first non-empty element in the block */ - while (!(v & bitmask[bs_start])) { - bs_start ++; + /* Emit OnSet events for newly inherited components */ + if (storage_i != -1) { + bool override = false; - /* Block was not empty, so bs_start must be smaller than 64 */ - ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); + /* 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; } - /* 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; - } + if (ider_onset_count) { + it->event = er_onset->event; - v = data[bs_block_end]; - remain = BS_MAX; /* Test the full block */ - } + 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, evtx); - /* Step 4: find remainder of enabled elements in current block */ - if (bs_block_end != bs_block_count) { - while ((v & bitmask[bs_end])) { - bs_end ++; + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke( + world, &ider->self_up, it, table, trav, evtx); + } else if (override) { + ecs_entity_t src = it->sources[0]; + it->sources[0] = 0; + flecs_observers_invoke(world, &ider->self, it, table, 0, evtx); + flecs_observers_invoke(world, &ider->self_up, it, table, 0, evtx); + it->sources[0] = src; + } } - } - - /* 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; + it->event = event; + it->ptrs[0] = base_ptr; } + } +} - /* 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; - } +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, + int32_t evtx) +{ + /* 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); - /* 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; - } - } + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, + tgt, tgt_table, column, offset, trav, evtx); +} - last = cur_last; - int32_t elem_count = last - first; +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); - /* 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; - } + for (sp = 0; sp < stack_count; sp ++) { + ecs_table_t *elem = stack_elems[sp]; + if (flecs_id_record_get_table(idr, elem)) { + break; } - - 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; + return sp; } -#undef BS_MAX +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 -int32_t flecs_get_flattened_target( +void flecs_emit_forward_cached_ids( 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) + 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, + int32_t evtx) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; - } + 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_record_t *r = cur->target; - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + 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); - ecs_table_t *table = r->table; - if (!table) { - return -1; - } + if (flecs_emit_stack_has(stack, rc_idr)) { + continue; + } - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (tr) { - *src_out = ecs_record_get_entity(r); - *tr_out = (ecs_table_record_t*)tr; - return tr->column; + 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, evtx); } +} - if (table->flags & EcsTableHasTarget) { - int32_t col = table->storage_map[table->_->ft_offset]; - ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); - EcsTarget *next = table->data.columns[col].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); +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"); } - - return ecs_search_relation( - world, table, 0, id, rel, EcsSelf|EcsUp, src_out, NULL, tr_out); } -void flecs_entity_filter_init( +static +void flecs_emit_forward_table_up( 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) + 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, + int32_t evtx) { - 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; + 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); - /* Look for union fields */ - if (table->flags & EcsTableHasUnion) { - for (i = 0; i < term_count; i ++) { - if (ecs_term_match_0(&terms[i])) { - continue; - } + /* 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); + } - 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; - } + 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(); - ecs_id_t table_id = table->type.array[column - 1]; - if (ECS_PAIR_FIRST(table_id) != EcsUnion) { - continue; - } + /* 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; - 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(id); - el->sw_column = NULL; - ids[field] = id; - has_filter = true; + 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; } - } - /* Look for disabled fields */ - if (table->flags & EcsTableHasToggle) { - for (i = 0; i < term_count; i ++) { - if (ecs_term_match_0(&terms[i])) { - 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; - 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_search(world, table, bs_id, 0); + 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, evtx); + 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, evtx); + if (++i >= id_count) { + break; + } - 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; + 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, evtx); } - /* 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->column; - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t rel = ecs_pair_second(world, table->type.array[column]); + 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); - for (i = 0; i < term_count; i ++) { - if (ecs_term_match_0(&terms[i])) { - continue; - } + /* Only add ids that were added for this table */ + int32_t count = ecs_vec_count(reachable_ids); + count -= rc_child_offset; - if (terms[i].src.trav == rel) { - ef.flat_tree_column = table->storage_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); - } + /* 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); } - } - if (has_filter) { - *entity_filter = ecs_os_malloc_t(ecs_entity_filter_t); - ecs_assert(*entity_filter != NULL, ECS_OUT_OF_MEMORY, NULL); - **entity_filter = ef; + 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(); } -void flecs_entity_filter_fini( +static +void flecs_emit_forward_up( ecs_world_t *world, - ecs_entity_filter_t *ef) + 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, + int32_t evtx) { - if (!ef) { + 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; } - 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); + flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, + tgt, tgt_table, tgt_record, idr, stack, reachable_ids, evtx); } -int flecs_entity_filter_next( - ecs_entity_filter_iter_t *it) +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, + int32_t evtx) { - 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; + ecs_reachable_cache_t *rc = &idr->reachable; - do { - found = false; + 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(); - 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; - } + 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, evtx); + 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 (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 (ecs_should_log_3()) { + ecs_dbg_3("cache after rebuild:"); + flecs_emit_dump_cache(world, &rc->ids); } - 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].array; - int32_t ft_offset; - int32_t ft_count; + 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); + } - 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_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(ft_offset < ecs_table_count(table), + 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); - EcsTarget *cur = &ft[ft_offset]; - ft_count = cur->count; - bool is_last = (ft_offset + ft_count) >= range_end; + ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); - 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); + 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, evtx); + } + } +} - /* 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; - } - } +/* 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); - range->offset = ft_offset; - range->count = ft_count; - it->prev = table; - } - } while (!found); + ecs_time_t t = {0}; + bool measure_time = world->flags & EcsWorldMeasureSystemTime; + if (measure_time) { + ecs_time_measure(&t); + } - it->prev = table; + 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; - if (!found) { - return EcsIterNext; - } else { - return result; + /* Deferring cannot be suspended for observers */ + int32_t defer = world->stages[0].defer; + if (defer < 0) { + world->stages[0].defer *= -1; } -} -/** - * @file addons/log.c - * @brief Log addon. - */ + /* 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; -#ifdef FLECS_LOG + 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; -#include + ecs_iter_t it = { + .world = stage, + .real_world = world, + .event = event, + .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 + }; -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); + /* 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; - char *ptr, ch, prev = '\0'; - bool isNum = false; - char isStr = '\0'; - bool isVar = false; - bool overrideColor = false; - bool autoColor = true; - bool dontAppend = false; + ecs_observable_t *observable = ecs_get_observable(desc->observable); + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - for (ptr = msg; (ch = *ptr); ptr++) { - dontAppend = false; + /* 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); - 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; - } + 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); + } - 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; - } + int32_t id_count = ids->count; + ecs_id_t *id_array = ids->array; - if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); - isVar = false; - } + /* 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)); - if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); - isVar = true; - } - } + /* 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; - if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { - bool isColor = true; - overrideColor = true; + /* Set if event has been propagated */ + bool propagated = false; - /* 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; - } + /* Does table has observed entities */ + bool has_observed = table_flags & EcsTableHasTraversable; - if (isColor) { - ptr += 2; - while ((ch = *ptr) != ']') ptr ++; - dontAppend = true; + /* 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; + +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, evtx); } - if (!autoColor) { - overrideColor = true; + + 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 (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 (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, evtx); + flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA, evtx); + } + it.sources[0] = 0; + } + } + } + + it.ptrs[0] = ptr; + } else { + if (it.event == EcsUnSet) { + /* Only valid for components, not tags */ + continue; + } } } - if (!dontAppend) { - ecs_strbuf_appendstrn(buf, ptr, 1); + /* 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, evtx); + flecs_observers_invoke(world, &ider->self_up, &it, table, 0, evtx); } - if (!overrideColor) { - if (((ch == '\'') || (ch == '"')) && !isStr) { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + if (!ider_count || !count || !has_observed) { + continue; + } + + /* If event is propagated, we don't have to manually invalidate entities + * lower in the tree(s). */ + propagated = true; + + /* 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; + + ecs_record_t **recs = ecs_vec_get_t(&storage->records, + ecs_record_t*, offset); + for (r = 0; r < count; r ++) { + ecs_record_t *record = recs[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); } } - prev = ch; + it.table = table; + it.other_table = other_table; + it.entities = entities; + it.count = count; + it.offset = offset; + it.sources[0] = 0; } - if (isNum || isStr || isVar || overrideColor) { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + if (count && can_forward && has_observed && !propagated) { + flecs_emit_propagate_invalidate(world, table, offset, count); } -} -void _ecs_printv( - int level, - const char *file, - int32_t line, - const char *fmt, - va_list args) -{ - (void)level; - (void)line; + can_override = false; /* Don't override twice */ + can_unset = false; /* Don't unset twice */ + can_forward = false; /* Don't forward twice */ - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + 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; + } - /* 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); + if (wcer && er != wcer) { + /* Repeat event loop for Wildcard event */ + er = wcer; + it.event = event; + goto repeat_event; + } - char *msg = ecs_strbuf_get(&msg_buf); +error: + world->stages[0].defer = defer; - if (msg) { - ecs_os_api.log_(level, file, line, msg); - ecs_os_free(msg); - } else { - ecs_os_api.log_(level, file, line, ""); + if (measure_time) { + world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); } + return; } -void _ecs_print( - int level, - const char *file, - int32_t line, - const char *fmt, - ...) +void ecs_emit( + ecs_world_t *stage, + ecs_event_desc_t *desc) { - va_list args; - va_start(args, fmt); - _ecs_printv(level, file, line, fmt, args); - va_end(args); + ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); + 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; + } + flecs_emit(world, stage, desc); } -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; - } +/** + * @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. + */ - _ecs_printv(level, file, line, fmt, args); -} +#include -void _ecs_log( - int level, - const char *file, - int32_t line, - const char *fmt, - ...) +static +ecs_entity_t flecs_get_observer_event( + ecs_term_t *term, + ecs_entity_t event) { - if (level > ecs_os_api.log_level_) { - return; + /* If operator is Not, reverse the event */ + if (term->oper == EcsNot) { + if (event == EcsOnAdd) { + event = EcsOnRemove; + } else if (event == EcsOnRemove) { + event = EcsOnAdd; + } } - va_list args; - va_start(args, fmt); - _ecs_printv(level, file, line, fmt, args); - va_end(args); + return event; } - -void _ecs_log_push( - int32_t level) +static +ecs_flags32_t flecs_id_flag_for_event( + ecs_entity_t e) { - if (level <= ecs_os_api.log_level_) { - ecs_os_api.log_indent_ ++; + if (e == EcsOnAdd) { + return EcsIdHasOnAdd; } -} - -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); + 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; } -void _ecs_parser_errorv( - const char *name, - const char *expr, - int64_t column_arg, - const char *fmt, - va_list args) +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) { - 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; - } - } + 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 + }); - /* 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_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_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, '^'); + 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; } } - char *msg = ecs_strbuf_get(&msg_buf); - ecs_os_err(name, 0, msg); - ecs_os_free(msg); + flecs_event_id_record_remove(evt, id); + ecs_os_free(idt); } } -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) +static +void flecs_register_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + size_t offset) { - 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); - } -} + ecs_id_t term_id = observer->register_id; + ecs_term_t *term = &observer->filter.terms[0]; + ecs_entity_t trav = term->src.trav; -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; -} + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, observer->events[i]); -bool _ecs_assert( - bool condition, - int32_t err, - const char *cond_str, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - if (!condition) { - 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)); + /* 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); } - ecs_os_api.log_last_error_ = err; } - - return condition; } -void _ecs_deprecated( - const char *file, - int32_t line, - const char *msg) +static +void flecs_uni_observer_register( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) { - _ecs_err(file, line, "%s", msg); -} + ecs_term_t *term = &observer->filter.terms[0]; + ecs_flags32_t flags = term->src.flags; -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; + 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)); } -# 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) +static +void flecs_unregister_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + size_t offset) { - 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_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); - } + ecs_id_t term_id = observer->register_id; + ecs_term_t *term = &observer->filter.terms[0]; + ecs_entity_t trav = term->src.trav; - return "unknown error code"; -} + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, observer->events[i]); -#else + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_get(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); -/* Empty bodies for when logging is disabled */ + /* 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); -void _ecs_log( - int32_t level, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - (void)level; - (void)file; - (void)line; - (void)fmt; -} + 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); + } -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - (void)name; - (void)expr; - (void)column; - (void)fmt; + flecs_inc_observer_count(world, event, er, term_id, -1); + if (trav) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), -1); + } + } } -void _ecs_parser_errorv( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - va_list args) +static +void flecs_unregister_observer( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) { - (void)name; - (void)expr; - (void)column; - (void)fmt; - (void)args; -} + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + if (!observer->filter.terms) { + ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); + return; + } -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; + 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)); + } } -bool _ecs_assert( - bool condition, - int32_t error_code, - const char *condition_str, - const char *file, - int32_t line, - const char *fmt, - ...) +static +bool flecs_ignore_observer( + ecs_observer_t *observer, + ecs_table_t *table, + int32_t evtx) { - (void)condition; - (void)error_code; - (void)condition_str; - (void)file; - (void)line; - (void)fmt; - return true; -} + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -#endif + int32_t *last_event_id = observer->last_event_id; + if (last_event_id && last_event_id[0] == evtx) { + return true; + } -int ecs_log_get_level(void) { - return ecs_os_api.log_level_; -} + ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags; -int ecs_log_set_level( - int level) -{ - int prev = level; - ecs_os_api.log_level_ = level; - return prev; -} + bool result = (table_flags & EcsTableIsPrefab) && + !(filter_flags & EcsFilterMatchPrefab); + result = result || ((table_flags & EcsTableIsDisabled) && + !(filter_flags & EcsFilterMatchDisabled)); -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; + return result; } -bool ecs_log_enable_timestamp( - bool enabled) +static +bool flecs_is_simple_result( + ecs_iter_t *it) { - bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; - ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); - return prev; + return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0); } -bool ecs_log_enable_timedelta( - bool enabled) +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) { - bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; - ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); - return prev; -} + ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); -int ecs_log_last_error(void) -{ - int result = ecs_os_api.log_last_error_; - ecs_os_api.log_last_error_ = 0; - return result; -} + if (ecs_should_log_3()) { + char *path = ecs_get_fullpath(world, it->system); + ecs_dbg_3("observer: invoke %s", path); + ecs_os_free(path); + } -/** - * @file addons/pipeline/worker.c - * @brief Functions for running pipelines on one or more threads. - */ + ecs_log_push_3(); -/** - * @file addons/system/system.c - * @brief Internal types and functions for system addon. - */ + world->info.observers_ran_frame ++; -#ifndef FLECS_SYSTEM_PRIVATE_H -#define FLECS_SYSTEM_PRIVATE_H + 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); + } -#ifdef FLECS_SYSTEM + 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; + } -#define ecs_system_t_magic (0x65637383) -#define ecs_system_t_tag EcsSystem + ecs_log_pop_3(); +} -extern ecs_mixins_t ecs_system_t_mixins; +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; -typedef struct ecs_system_t { - ecs_header_t hdr; + 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_run_action_t run; /* See ecs_system_desc_t */ - ecs_iter_action_t action; /* See ecs_system_desc_t */ + ecs_log_push_3(); + flecs_observer_invoke(it->real_world, it, o, o->callback, 0, + flecs_is_simple_result(it)); + ecs_log_pop_3(); +} - 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; +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, + int32_t evtx, + bool simple_result) +{ + ecs_filter_t *filter = &observer->filter; + ecs_term_t *term = &filter->terms[0]; + if (flecs_ignore_observer(observer, table, evtx)) { + return; + } - 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 */ + ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); + if (trav && term->src.trav != trav) { + return; + } - void *ctx; /* Userdata for system */ - void *binding_ctx; /* Optional language binding context */ + 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->terms = term; - ecs_ctx_free_t ctx_free; - ecs_ctx_free_t binding_ctx_free; + ecs_entity_t event = it->event; + it->event = flecs_get_observer_event(term, event); - /* Mixins */ - ecs_world_t *world; - ecs_entity_t entity; - ecs_poly_dtor_t dtor; -} ecs_system_t; + 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); + } -/* 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); + it->event = event; +} -/* Internal function to run a system */ -ecs_entity_t ecs_run_intern( +void flecs_observers_invoke( 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 + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav, + int32_t evtx) +{ + if (ecs_map_is_init(observers)) { + ecs_table_lock(it->world, table); -#endif + 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, evtx, simple_result); + } + ecs_table_unlock(it->world, table); + } +} -#ifdef FLECS_PIPELINE -/** - * @file addons/pipeline/pipeline.h - * @brief Internal functions/types for pipeline addon. - */ +static +bool flecs_multi_observer_invoke(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + ecs_world_t *world = it->real_world; -#ifndef FLECS_PIPELINE_PRIVATE_H -#define FLECS_PIPELINE_PRIVATE_H + if (o->last_event_id[0] == world->event_id) { + /* Already handled this event */ + return false; + } + o->last_event_id[0] = world->event_id; -/** 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 */ - bool multi_threaded; /* Whether systems can be ran multi threaded */ - bool no_readonly; /* Whether systems are staged or not */ -} ecs_pipeline_op_t; - -typedef 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 */ -} ecs_pipeline_state_t; - -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); - -//////////////////////////////////////////////////////////////////////////////// -//// Worker API -//////////////////////////////////////////////////////////////////////////////// - -bool flecs_worker_begin( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_pipeline_state_t *pq, - bool start_of_frame); - -void flecs_worker_end( - ecs_world_t *world, - ecs_stage_t *stage); - -bool flecs_worker_sync( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_pipeline_state_t *pq, - ecs_pipeline_op_t **cur_op, - int32_t *cur_i); - -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); - -bool flecs_join_worker_threads( - ecs_world_t *world); - -#endif - - -typedef struct ecs_worker_state_t { - ecs_stage_t *stage; - ecs_pipeline_state_t *pq; -} ecs_worker_state_t; - -/* Worker thread */ -static -void* flecs_worker(void *arg) { - ecs_worker_state_t *state = arg; - ecs_stage_t *stage = state->stage; - ecs_pipeline_state_t *pq = state->pq; - ecs_world_t *world = stage->world; - - ecs_poly_assert(world, ecs_world_t); - ecs_poly_assert(stage, ecs_stage_t); + ecs_iter_t user_it = *it; + user_it.field_count = o->filter.field_count; + 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; - ecs_dbg_2("worker %d: start", stage->id); + flecs_iter_init(it->world, &user_it, flecs_iter_cache_all); + user_it.flags |= (it->flags & EcsIterTableOnly); - /* Start worker, increase counter so main thread knows how many - * workers are ready */ - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running ++; + 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]; - if (!(world->flags & EcsWorldQuitWorkers)) { - ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + int32_t column = it->columns[0]; + if (term->oper == EcsNot) { + table = it->other_table; + prev_table = it->table; } - 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((ecs_world_t*)stage, pq, world->info.delta_time); + if (!table) { + table = &world->store.root; + } + if (!prev_table) { + prev_table = &world->store.root; + } - ecs_set_scope((ecs_world_t*)stage, old_scope); + if (column < 0) { + column = -column; } - ecs_dbg_2("worker %d: finalizing", stage->id); + 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; - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running --; - ecs_os_mutex_unlock(world->sync_mutex); + 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; + } + } - ecs_dbg_2("worker %d: stop", stage->id); + /* 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); + } - ecs_os_free(state); + flecs_iter_populate_data(world, &user_it, it->table, it->offset, + it->count, user_it.ptrs); - return NULL; + 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; } -static -bool flecs_is_multithreaded( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - return ecs_get_stage_count(world) > 1; +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 -bool flecs_is_main_thread( - ecs_stage_t *stage) -{ - return !stage->id; +void flecs_default_multi_observer_run_callback(ecs_iter_t *it) { + flecs_multi_observer_invoke(it); } -/* 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_entity_t pipeline = world->pipeline; - ecs_assert(pipeline != 0, ECS_INVALID_OPERATION, NULL); - const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); - ecs_assert(pqc != NULL, ECS_INVALID_OPERATION, NULL); - ecs_pipeline_state_t *pq = pqc->state; - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_worker_state_t *state = ecs_os_calloc_t(ecs_worker_state_t); - state->stage = stage; - state->pq = pq; - - 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, state); - } - else - { - /* workers are using long-running os threads */ - stage->thread = ecs_os_thread_new(flecs_worker, state); - } - ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); +/* 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_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); +void flecs_multi_observer_builtin_run(ecs_iter_t *it) { + ecs_observer_t *observer = it->ctx; + ecs_run_action_t run = observer->run; - if (!ecs_using_task_threads(world)) - { - flecs_create_worker_threads(world); + 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); } } -/* Wait until all workers are running */ static -void flecs_wait_for_workers( - ecs_world_t *world) +void flecs_uni_observer_yield_existing( + ecs_world_t *world, + ecs_observer_t *observer) { - ecs_poly_assert(world, ecs_world_t); + ecs_iter_action_t callback = observer->callback; - int32_t stage_count = ecs_get_stage_count(world); - if (stage_count <= 1) { - return; - } + ecs_defer_begin(world); - bool wait = true; - do { - ecs_os_mutex_lock(world->sync_mutex); - if (world->workers_running == (stage_count - 1)) { - wait = false; + /* 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_os_mutex_unlock(world->sync_mutex); - } while (wait); -} - -/* Wait until all threads are waiting on sync point */ -static -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_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_os_mutex_lock(world->sync_mutex); - if (world->workers_waiting != (stage_count - 1)) { - ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + 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]; + callback(&it); + } } - /* 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"); + ecs_defer_end(world); } -/* Synchronize workers */ static -void flecs_sync_worker( - ecs_world_t *world) +void flecs_multi_observer_yield_existing( + ecs_world_t *world, + ecs_observer_t *observer) { - int32_t stage_count = ecs_get_stage_count(world); - if (stage_count <= 1) { - return; + ecs_run_action_t run = observer->run; + if (!run) { + run = flecs_default_multi_observer_run_callback; } - /* 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); - } + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - /* 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); -} + ecs_defer_begin(world); -/* Signal workers that they can start/resume work */ -static -void flecs_signal_workers( - ecs_world_t *world) -{ - int32_t stage_count = ecs_get_stage_count(world); - if (stage_count <= 1) { + int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); + if (pivot_term < 0) { 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); -} - -bool 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 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; } - stage->thread = 0; - }; - - /* If no threads are active, just return */ - if (!threads_active) { - return false; - } - /* 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); + ecs_iter_t it; + iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); + 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; - /* Join all threads with main */ - for (i = 1; i < count; i ++) { - if (ecs_using_task_threads(world)) - { - /* Join using the override provided */ - ecs_os_task_join(stages[i].thread); - } - else - { - ecs_os_thread_join(stages[i].thread); + 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]; + run(&it); + world->event_id ++; } - stages[i].thread = 0; } - world->flags &= ~EcsWorldQuitWorkers; - ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); - - return true; + ecs_defer_end(world); } -/** Stop workers */ static -bool ecs_stop_threads( - ecs_world_t *world) -{ - /* Join all existing worker threads */ - if (flecs_join_worker_threads(world)) - { - /* Deinitialize stages */ - ecs_set_stage_count(world, 1); - - return true; - } - - return false; -} - -/* -- Private functions -- */ -bool flecs_worker_begin( +int flecs_uni_observer_init( ecs_world_t *world, - ecs_stage_t *stage, - ecs_pipeline_state_t *pq, - bool start_of_frame) + ecs_observer_t *observer, + const ecs_observer_desc_t *desc) { - ecs_poly_assert(world, ecs_world_t); - ecs_poly_assert(stage, ecs_stage_t); - bool main_thread = flecs_is_main_thread(stage); - bool multi_threaded = flecs_is_multithreaded(world); - - if (main_thread) { - if (ecs_stage_is_readonly(world)) { - ecs_assert(!pq->no_readonly, ECS_INTERNAL_ERROR, NULL); - ecs_readonly_end(world); - pq->no_readonly = false; - } - - flecs_pipeline_update(world, pq, start_of_frame); + 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; - ecs_pipeline_op_t *cur_op = pq->cur_op; - if (main_thread && (cur_op != NULL)) { - pq->no_readonly = cur_op->no_readonly; - if (!cur_op->no_readonly) { - ecs_readonly_begin(world); + 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; + } } - - ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, - cur_op->multi_threaded); - ecs_assert(world->workers_waiting == 0, - ECS_INTERNAL_ERROR, NULL); } - if (main_thread && multi_threaded) { - flecs_signal_workers(world); + flecs_uni_observer_register(world, observer->observable, observer); + + if (desc->yield_existing) { + flecs_uni_observer_yield_existing(world, observer); } - return pq->cur_op != NULL; + return 0; } -void flecs_worker_end( +static +int flecs_multi_observer_init( ecs_world_t *world, - ecs_stage_t *stage) + ecs_observer_t *observer, + const ecs_observer_desc_t *desc) { - ecs_poly_assert(world, ecs_world_t); - ecs_poly_assert(stage, ecs_stage_t); + /* 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; - if (flecs_is_multithreaded(world)) { - if (flecs_is_main_thread(stage)) { - flecs_wait_for_sync(world); - } else { - flecs_sync_worker(world); + /* 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 (flecs_is_main_thread(stage)) { - if (ecs_stage_is_readonly(world)) { - ecs_readonly_end(world); - } + if (filter->flags & EcsFilterMatchPrefab) { + child_desc.filter.flags |= EcsFilterMatchPrefab; + } + if (filter->flags & EcsFilterMatchDisabled) { + child_desc.filter.flags |= EcsFilterMatchDisabled; } -} -bool flecs_worker_sync( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_pipeline_state_t *pq, - ecs_pipeline_op_t **cur_op, - int32_t *cur_i) -{ - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->cur_op != NULL, ECS_INTERNAL_ERROR, NULL); - bool main_thread = flecs_is_main_thread(stage); + /* Create observers as children of observer */ + ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity); - /* Synchronize workers */ - flecs_worker_end(world, stage); + for (i = 0; i < term_count; i ++) { + if (filter->terms[i].src.flags & EcsFilter) { + continue; + } - /* Store the current state of the schedule after we synchronized the - * threads, to avoid race conditions. */ - if (main_thread) { - pq->cur_op = *cur_op; - pq->cur_i = *cur_i; - } + ecs_term_t *term = &child_desc.filter.terms[0]; + child_desc.term_index = filter->terms[i].field_index; + *term = filter->terms[i]; - /* Prepare state for running the next part of the schedule */ - bool result = flecs_worker_begin(world, stage, pq, false); - *cur_op = pq->cur_op; - *cur_i = pq->cur_i; - return result; -} + ecs_oper_kind_t oper = term->oper; + ecs_id_t id = term->id; -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); + /* 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; - /* Make sure workers are running and ready */ - flecs_wait_for_workers(world); + /* 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; + } - /* 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); -} + term->first.name = NULL; + term->first.id = ti_ids[ti]; + term->id = ti_ids[ti]; -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) { - if (ecs_stop_threads(world)) { - ecs_os_cond_free(world->worker_cond); - ecs_os_cond_free(world->sync_cond); - ecs_os_mutex_free(world->sync_mutex); + if (ecs_observer_init(world, &child_desc) == 0) { + goto error; + } } + continue; } - /* Adopt the desired API for worker creation & join */ - world->workers_use_task_api = use_task_api; + /* Single component observers never use OR */ + if (oper == EcsOr) { + term->oper = EcsAnd; + } - /* 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); + /* 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; } } -} -/* -- Public functions -- */ + ecs_set_scope(world, old_scope); -void ecs_set_threads( - ecs_world_t *world, - int32_t threads) -{ - flecs_set_threads_internal(world, threads, false /* use thread API*/); + if (desc->yield_existing) { + flecs_multi_observer_yield_existing(world, observer); + } + + return 0; +error: + return -1; } -void ecs_set_task_threads( +ecs_entity_t ecs_observer_init( ecs_world_t *world, - int32_t task_threads) + const ecs_observer_desc_t *desc) { - flecs_set_threads_internal(world, task_threads, true /* use task API*/); -} + 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); -bool ecs_using_task_threads( - ecs_world_t *world) -{ - return world->workers_use_task_api; -} + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_new(world, 0); + } -#endif + 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); -/** - * @file addons/ipeline/pipeline.c - * @brief Functions for building and running pipelines. - */ + 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; -#ifdef FLECS_PIPELINE + /* Parse filter */ + if (ecs_filter_init(world, &filter_desc) == NULL) { + flecs_observer_fini(observer); + return 0; + } -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); - } -} + /* Observer must have at least one term */ + ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL); -static ECS_MOVE(EcsPipeline, dst, src, { - flecs_pipeline_free(dst->state); - dst->state = src->state; - src->state = NULL; -}) + poly->poly = observer; -static ECS_DTOR(EcsPipeline, ptr, { - flecs_pipeline_free(ptr->state); -}) + ecs_observable_t *observable = desc->observable; + if (!observable) { + observable = ecs_get_observable(world); + } -typedef enum ecs_write_kind_t { - WriteStateNone = 0, - WriteStateToStage, -} ecs_write_kind_t; + 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; -typedef struct ecs_write_state_t { - bool write_barrier; - ecs_map_t ids; - ecs_map_t wildcard_ids; -} ecs_write_state_t; + /* 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; + } -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 (event == EcsMonitor) { + /* Monitor event must be first and last event */ + ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); - if (write_state->write_barrier) { - /* Any component could have been written */ - return WriteStateToStage; - } + observer->events[0] = EcsOnAdd; + observer->events[1] = EcsOnRemove; + observer->event_count ++; + observer->is_monitor = true; + } else { + observer->events[i] = event; + } - 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; + observer->event_count ++; } - } - if (!ecs_id_is_wildcard(id)) { - if (ecs_map_get(&write_state->ids, id)) { - result = WriteStateToStage; + /* 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; } - } 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 (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_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; + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]observer#[reset] %s created", + ecs_get_name(world, entity)); + } + } else { + ecs_observer_t *observer = ecs_poly(poly->poly, ecs_observer_t); + + 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; + } } - return result; + ecs_poly_modified(world, entity, ecs_observer_t); + + return entity; +error: + ecs_delete(world, entity); + return 0; } -static -void flecs_pipeline_set_write_state( - ecs_write_state_t *write_state, - ecs_id_t id) +void* ecs_observer_get_ctx( + const ecs_world_t *world, + ecs_entity_t observer) { - 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; + 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 { - ids = &write_state->ids; - } - - ecs_map_ensure(ids, id)[0] = true; + return NULL; + } } -static -void flecs_pipeline_reset_write_state( - ecs_write_state_t *write_state) +void* ecs_observer_get_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer) { - ecs_map_clear(&write_state->ids); - ecs_map_clear(&write_state->wildcard_ids); - write_state->write_barrier = false; + 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; + } } -static -bool flecs_pipeline_check_term( - ecs_world_t *world, - ecs_term_t *term, - bool is_active, - ecs_write_state_t *write_state) +void flecs_observer_fini( + ecs_observer_t *observer) { - (void)world; - - ecs_term_id_t *src = &term->src; - if (src->flags & EcsInOutNone) { - return false; + 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 */ + } } - 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); + /* Cleanup filters */ + ecs_filter_fini(&observer->filter); - 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; + /* Cleanup context */ + if (observer->ctx_free) { + observer->ctx_free(observer->ctx); } - 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 (observer->binding_ctx_free) { + observer->binding_ctx_free(observer->binding_ctx); } - 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; - } + ecs_poly_free(observer, ecs_observer_t); +} - 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; - default: - break; - } +/** + * @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. + */ - 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; - } - default: - break; - } - } +#include +#include - return false; -} +void ecs_os_api_impl(ecs_os_api_t *api); -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; +static bool ecs_os_api_initialized = false; +static bool ecs_os_api_initializing = false; +static int ecs_os_api_init_count = 0; - /* 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); - } - } +#ifndef __EMSCRIPTEN__ +ecs_os_api_t ecs_os_api = { + .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, + .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +}; +#else +/* Disable colors by default for emscripten */ +ecs_os_api_t ecs_os_api = { + .flags_ = EcsOsApiHighResolutionTimer, + .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +}; +#endif - /* 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); - } - } +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; - return needs_merge; +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; + } } -static -EcsPoly* flecs_pipeline_term_system( - ecs_iter_t *it) -{ - int32_t index = ecs_search(it->real_world, it->table, - ecs_poly_id(EcsSystem), 0); - 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; +ecs_os_api_t ecs_os_get_api(void) { + return ecs_os_api; } -static -bool flecs_pipeline_build( - ecs_world_t *world, - ecs_pipeline_state_t *pq) +void ecs_os_init(void) { - 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; + 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_(); + } + } +} - 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); +void ecs_os_fini(void) { + if (!--ecs_os_api_init_count) { + if (ecs_os_api.fini_) { + ecs_os_api.fini_(); + } + } +} - bool multi_threaded = false; - bool no_readonly = false; - bool first = true; +/* 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 - /* 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_index(world, it.table, EcsEmpty) == -1; +#if HAVE_EXECINFO +#include +#define ECS_BT_BUF_SIZE 100 - int32_t i; - for (i = 0; i < it.count; i ++) { - ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); - ecs_query_t *q = sys->query; +void flecs_dump_backtrace( + void *stream) +{ + int nptrs; + void *buffer[ECS_BT_BUF_SIZE]; + char **strings; - bool needs_merge = false; - needs_merge = flecs_pipeline_check_terms( - world, &q->filter, is_active, &ws); + nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); - if (is_active) { - if (first) { - multi_threaded = sys->multi_threaded; - no_readonly = sys->no_readonly; - first = false; - } + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + return; + } - 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; - } - } + for (int j = 1; j < nptrs; j++) { + fprintf(stream, "%s\n", strings[j]); + } - if (no_readonly) { - needs_merge = true; - } + free(strings); +} +#else +void flecs_dump_backtrace( + void *stream) +{ + (void)stream; +} +#endif +#undef HAVE_EXECINFO_H - if (needs_merge) { - /* After merge all components will be merged, so reset state */ - flecs_pipeline_reset_write_state(&ws); +static +void flecs_log_msg( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + FILE *stream; + if (level >= 0) { + stream = stdout; + } else { + stream = stderr; + } - /* 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; - } + bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; + bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; - /* 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); - } + time_t now = 0; - /* 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 (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 (!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; + if (delta) { + if (delta < 10) { + fputs(" ", stream); } - - /* 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 (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 (op && !op->count && ecs_vec_count(&pq->ops) > 1) { - ecs_vec_remove_last(&pq->ops); + 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); } - 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(); + 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); + } - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op->multi_threaded, !op->no_readonly); - ecs_log_push_1(); + if (use_colors) fputs(ECS_NORMAL, stream); + fputs(": ", stream); - 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_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t); + 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; -#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; + for (i = 0; i < indent_count; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; } -#endif - if (doc_name) { - ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); - } else { - ecs_dbg("#[green]system#[reset] %s", path); + + if (ecs_os_api.log_indent_ != indent_count) { + indent[i * 2 - 2] = '+'; } - ecs_os_free(path); -#endif - ecs_assert(op[op_index].offset + ran_since_merge == i, - ECS_INTERNAL_ERROR, NULL); + indent[i * 2] = '\0'; - 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(); + fputs(indent, stream); + } + } + + if (level < 0) { + if (file) { + const char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); } - 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; - } + if (file_ptr) { + file = file_ptr + 1; } + + fputs(file, stream); + fputs(": ", stream); } - ecs_log_pop_1(); - ecs_log_pop_1(); + if (line) { + fprintf(stream, "%d: ", line); + } } - pq->match_count = pq->query->match_count; + fputs(msg, stream); - ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t), - ECS_INTERNAL_ERROR, NULL); + fputs("\n", stream); - return true; + if (level == -4) { + flecs_dump_backtrace(stream); + } } -static -void flecs_pipeline_next_system( - ecs_pipeline_state_t *pq) +void ecs_os_dbg( + const char *file, + int32_t line, + const char *msg) { - if (!pq->cur_op) { - return; + if (ecs_os_api.log_) { + ecs_os_api.log_(1, file, line, msg); } - - 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) +void ecs_os_trace( + const char *file, + int32_t line, + const char *msg) { - 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); + if (ecs_os_api.log_) { + ecs_os_api.log_(0, file, line, msg); } +} - 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); +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); } - - return rebuilt; } -void ecs_run_pipeline( - ecs_world_t *world, - ecs_entity_t pipeline, - ecs_ftime_t delta_time) +void ecs_os_err( + const char *file, + int32_t line, + const char *msg) { - if (!pipeline) { - pipeline = world->pipeline; + if (ecs_os_api.log_) { + ecs_os_api.log_(-3, file, line, msg); } - - EcsPipeline *pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); - flecs_pipeline_update(world, pq->state, true); - flecs_run_pipeline((ecs_world_t*)flecs_stage_from_world(&world), - pq->state, delta_time); } -void flecs_run_pipeline( - ecs_world_t *world, - ecs_pipeline_state_t *pq, - ecs_ftime_t delta_time) +void ecs_os_fatal( + const char *file, + int32_t line, + const char *msg) { - 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); - - if (!flecs_worker_begin(world, stage, pq, true)) { - return; + if (ecs_os_api.log_) { + ecs_os_api.log_(-4, file, line, msg); } +} - ecs_time_t st = {0}; - bool main_thread = !stage_index; - bool measure_time = main_thread && (world->flags & EcsWorldMeasureSystemTime); - ecs_pipeline_op_t *op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); - int32_t i = 0; - - do { - 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; - - if (i == count) { - break; - } - - if (measure_time) { - ecs_time_measure(&st); - } +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; - for (; i < count; i ++) { - /* Run system if: - * - this is the main thread, or if - * - the system is multithreaded - */ - if (main_thread || op->multi_threaded) { - ecs_entity_t system = systems[i]; - const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); - ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t); - - /* 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; - } + assert(sec < UINT32_MAX); + assert((now - sec * 1000000000) < UINT32_MAX); - ecs_run_intern(world, s, system, sys, stage_index, - stage_count, delta_time, 0, 0, NULL); - } + time->sec = (uint32_t)sec; + time->nanosec = (uint32_t)(now - sec * 1000000000); +} - world->info.systems_ran_frame ++; - ran_since_merge ++; +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); +} - if (ran_since_merge == op->count) { - /* Merge */ - break; - } - } +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); +} - if (measure_time) { - /* Don't include merge time in system time */ - world->info.system_time_total += - (ecs_ftime_t)ecs_time_measure(&st); - } +static +void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - /* Synchronize workers, rebuild pipeline if necessary. Pass current op - * and system index to function, so we know where to resume from. */ - } while (flecs_worker_sync(world, stage, pq, &op, &i)); + 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); +} - if (measure_time) { - world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + ecs_os_linc(&ecs_os_api_free_count); } + free(ptr); +} - flecs_worker_end(world, stage); +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; + } +} - return; +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 -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; +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; + } } - 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 */ + return base; +} - /* 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(); +static +char* ecs_os_api_module_to_dl(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - /* 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(); + /* Best guess, use module name with underscores + OS library extension */ + char *file_base = module_file_base(module, '_'); - ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); - ecs_log_push_2(); - ecs_delete(world, start_pip); - ecs_log_pop_2(); +# 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 - world->stage_count = stage_count; - ecs_log_pop_2(); + ecs_os_free(file_base); -error: - return; + return ecs_strbuf_get(&lib); } -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); +static +char* ecs_os_api_module_to_etc(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - if (ecs_using_task_threads(world)) - { - /* task threads were temporary and may now be joined */ - flecs_join_worker_threads(world); - } + /* Best guess, use module name with dashes + /etc */ + char *file_base = module_file_base(module, '-'); - return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); -error: - return false; -} + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, "/etc"); -void ecs_set_time_scale( - ecs_world_t *world, - ecs_ftime_t scale) -{ - world->info.time_scale = scale; -} + ecs_os_free(file_base); -void ecs_reset_clock( - ecs_world_t *world) -{ - world->info.world_time_total = 0; - world->info.world_time_total_raw = 0; + return ecs_strbuf_get(&lib); } -void ecs_set_pipeline( - ecs_world_t *world, - ecs_entity_t pipeline) +void ecs_os_set_api_defaults(void) { - ecs_poly_assert(world, ecs_world_t); - ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, - ECS_INVALID_PARAMETER, "not a pipeline"); - - int32_t thread_count = ecs_get_stage_count(world); - if (thread_count > 1) { - ecs_set_threads(world, 1); + /* Don't overwrite if already initialized */ + if (ecs_os_api_initialized != 0) { + return; } - world->pipeline = pipeline; - if (thread_count > 1) { - ecs_set_threads(world, thread_count); + + if (ecs_os_api_initializing != 0) { + return; } -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_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; -ecs_entity_t ecs_pipeline_init( - ecs_world_t *world, - const ecs_pipeline_desc_t *desc) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + /* Strings */ + ecs_os_api.strdup_ = ecs_os_api_strdup; - ecs_entity_t result = desc->entity; - if (!result) { - result = ecs_new(world, 0); - } + /* Time */ + ecs_os_api.get_time_ = ecs_os_gettime; - ecs_query_desc_t qd = desc->query; - if (!qd.order_by) { - qd.order_by = flecs_entity_compare; + /* 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; } - qd.filter.entity = result; - ecs_query_t *query = ecs_query_init(world, &qd); - if (!query) { - ecs_delete(world, result); - return 0; + if (!ecs_os_api.module_to_etc_) { + ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; } - ecs_assert(query->filter.terms[0].id == EcsSystem, - ECS_INVALID_PARAMETER, NULL); + ecs_os_api.abort_ = abort; - 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 }); +# 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 - return result; + ecs_os_api_initializing = false; } -/* -- 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); +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); } -#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); - } +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); } -void FlecsPipelineImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsPipeline); - ECS_IMPORT(world, FlecsSystem); - - 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); +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); +} - 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); +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL) && + (ecs_os_api.now_ != NULL); +} - ecs_set_hooks(world, EcsPipeline, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsPipeline), - .move = ecs_move(EcsPipeline) - }); +bool ecs_os_has_logging(void) { + return (ecs_os_api.log_ != NULL); +} - 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 - } - }); +bool ecs_os_has_dl(void) { + return + (ecs_os_api.dlopen_ != NULL) && + (ecs_os_api.dlproc_ != NULL) && + (ecs_os_api.dlclose_ != NULL); +} - /* Cleanup thread administration when world is destroyed */ - ecs_atfini(world, FlecsPipelineFini, 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 addons/monitor.c - * @brief Monitor addon. + * @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. */ -#ifdef FLECS_MONITOR - -ECS_COMPONENT_DECLARE(FlecsMonitor); -ECS_COMPONENT_DECLARE(EcsWorldStats); -ECS_COMPONENT_DECLARE(EcsPipelineStats); +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_entity_t EcsPeriod1s = 0; -ecs_entity_t EcsPeriod1m = 0; -ecs_entity_t EcsPeriod1h = 0; -ecs_entity_t EcsPeriod1d = 0; -ecs_entity_t EcsPeriod1w = 0; +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) + } +}; -static int32_t flecs_day_interval_count = 24; -static int32_t flecs_week_interval_count = 168; +ecs_mixins_t ecs_stage_t_mixins = { + .type_name = "ecs_stage_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_stage_t, world) + } +}; -static -ECS_COPY(EcsPipelineStats, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); -}) +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) + } +}; -static -ECS_MOVE(EcsPipelineStats, dst, src, { - ecs_os_memcpy_t(dst, src, EcsPipelineStats); - ecs_os_zeromem(src); -}) +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) + } +}; -static -ECS_DTOR(EcsPipelineStats, ptr, { - ecs_pipeline_stats_fini(&ptr->stats); -}) +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 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); +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); - ecs_ftime_t elapsed = hdr->elapsed; - hdr->elapsed += it->delta_time; + const ecs_mixins_t *mixins = hdr->mixins; + ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); - 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_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; - ecs_world_stats_t last_world = {0}; - ecs_pipeline_stats_t last_pipeline = {0}; - void *last = NULL; + /* Object has mixin, return its address */ + return ECS_OFFSET(hdr, offset); +} - 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); - } - } +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); - 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); - } + ecs_header_t *hdr = poly; + ecs_os_memset(poly, 0, size); - 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; - } + hdr->magic = ECS_OBJECT_MAGIC; + hdr->type = type; + hdr->mixins = mixins; - if (last && kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_fini(last); - } + return poly; } -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); +void ecs_poly_fini_( + ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + (void)type; - ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); + ecs_header_t *hdr = poly; - 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); - } + /* 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; } -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; - } +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); } - /* 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); + /* Never defer creation of a poly object */ + bool deferred = false; + if (ecs_is_deferred(world)) { + deferred = true; + ecs_defer_suspend(world); } - 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); - } - } + /* 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); - /* A day has 60 24 minute intervals */ - dst_hdr->reduce_count ++; - if (dst_hdr->reduce_count >= interval) { - dst_hdr->reduce_count = 0; + if (deferred) { + ecs_defer_resume(world); } - if (last && kind == ecs_id(EcsPipelineStats)) { - ecs_pipeline_stats_fini(last); - } + return result; } -static -void flecs_stats_monitor_import( +void ecs_poly_modified_( ecs_world_t *world, - ecs_id_t kind, - size_t size) + ecs_entity_t entity, + ecs_entity_t tag) { - 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 - }); + ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); +} - // 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 - }); +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); +} - // 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_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; +} - ecs_set_scope(world, prev); +#ifndef FLECS_NDEBUG +#define assert_object(cond, file, line, type_name)\ + ecs_assert_((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\ + assert(cond) - 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); +void* ecs_poly_assert_( + const ecs_poly_t *poly, + int32_t type, + const char *file, + int32_t line) +{ + assert_object(poly != NULL, file, line, 0); + + const ecs_header_t *hdr = poly; + const char *type_name = hdr->mixins->type_name; + assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name); + assert_object(hdr->type == type, file, line, type_name); + return ECS_CONST_CAST(ecs_poly_t*, poly); } +#endif -static -void flecs_world_monitor_import( - ecs_world_t *world) +bool ecs_poly_is_( + const ecs_poly_t *poly, + int32_t type) { - ECS_COMPONENT_DEFINE(world, EcsWorldStats); + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), - sizeof(EcsWorldStats)); + const ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + return hdr->type == type; } -static -void flecs_pipeline_monitor_import( - ecs_world_t *world) +ecs_iterable_t* ecs_get_iterable( + const ecs_poly_t *poly) { - 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)); + return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); } -void FlecsMonitorImport( - ecs_world_t *world) +ecs_observable_t* ecs_get_observable( + const ecs_poly_t *poly) { - ECS_MODULE_DEFINE(world, FlecsMonitor); - ECS_IMPORT(world, FlecsPipeline); - ECS_IMPORT(world, FlecsTimer); - - 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"); + return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); +} - 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); +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); } -#endif +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 addons/timer.c - * @brief Timer addon. + * @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. */ -#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}); +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 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; - } +void flecs_query_compute_group_id( + ecs_query_t *query, + ecs_query_table_match_t *match) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - 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 (query->group_by) { + ecs_table_t *table = match->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (timer[i].single_shot) { - timer[i].active = false; - } - } else { - timer[i].time = time_elapsed; - } + match->group_id = flecs_query_get_group_id(query, table); + } else { + match->group_id = 0; } } 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; - } - } +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 -void ProgressTickSource(ecs_iter_t *it) { - EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1); +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 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; + 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; } -ecs_entity_t ecs_set_timeout( - ecs_world_t *world, - ecs_entity_t timer, - ecs_ftime_t timeout) +static +void flecs_query_remove_group( + ecs_query_t *query, + uint64_t id) { - 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; + 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); + } } -error: - return timer; + ecs_map_remove_free(&query->groups, id); } -ecs_ftime_t ecs_get_timeout( - const ecs_world_t *world, - ecs_entity_t timer) +static +uint64_t flecs_query_default_group_by( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); + (void)ctx; - const EcsTimer *value = ecs_get(world, timer, EcsTimer); - if (value) { - return value->timeout; + ecs_id_t match; + if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { + return ecs_pair_second(world, match); } -error: return 0; } -ecs_entity_t ecs_set_interval( - ecs_world_t *world, - ecs_entity_t timer, - ecs_ftime_t interval) +/* 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) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + /* Grouping must be enabled */ + ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); - timer = ecs_set(world, timer, EcsTimer, { - .timeout = interval, - .active = true - }); + 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; - ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); - if (system_data) { - system_data->tick_source = timer; - } -error: - return timer; -} + /* Find closest smaller group id */ + while (ecs_map_next(&it)) { + id = ecs_map_key(&it); + if (id >= group_id) { + continue; + } -ecs_ftime_t ecs_get_interval( - const ecs_world_t *world, - ecs_entity_t timer) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + list = ecs_map_ptr(&it); + if (!list->last) { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } - if (!timer) { - return 0; + if (!closest_list || ((group_id - id) < (group_id - closest_id))) { + closest_id = id; + closest_list = list; + } } - const EcsTimer *value = ecs_get(world, timer, EcsTimer); - if (value) { - return value->timeout; + if (closest_list) { + return closest_list->last; + } else { + return NULL; /* Group should be first in query */ } -error: - return 0; } -void ecs_start_timer( - ecs_world_t *world, - ecs_entity_t timer) +/* Initialize group with first node */ +static +void flecs_query_create_group( + ecs_query_t *query, + ecs_query_table_match_t *match) { - 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; -} - -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); + uint64_t group_id = match->group_id; - filter = ecs_set(world, filter, EcsRateFilter, { - .rate = rate, - .src = source - }); + /* 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); - ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t); - if (system_data) { - system_data->tick_source = filter; - } + 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); -error: - return filter; + /* 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; + } + } } -void ecs_set_tick_source( - ecs_world_t *world, - ecs_entity_t system, - ecs_entity_t tick_source) +/* 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) { - 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; + if (query->group_by) { + return flecs_query_get_group(query, match->group_id); + } else { + return &query->list; + } } -void FlecsTimerImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsTimer); +/* 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; + } +} - ECS_IMPORT(world, FlecsPipeline); +/* 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_set_name_prefix(world, "Ecs"); + 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); - flecs_bootstrap_component(world, EcsTimer); - flecs_bootstrap_component(world, EcsRateFilter); + ecs_query_table_list_t *list = flecs_query_get_node_list(query, match); - /* 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 - }); + 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; + } - /* 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 - }); + ecs_assert(prev != NULL || query->list.first == match, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(next != NULL || query->list.last == match, + ECS_INTERNAL_ERROR, NULL); - /* 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 - }); + if (prev) { + prev->next = next; + } + if (next) { + next->prev = prev; + } - /* 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 - }); -} + ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + list->info.table_count --; -#endif + if (query->group_by) { + uint64_t group_id = match->group_id; -/** - * @file addons/flecs_cpp.c - * @brief Utilities for C++ addon. - */ + /* 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; + } -#include + ecs_assert(query->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); + query->list.info.table_count --; + list->info.match_count ++; -/* Utilities for C++ API */ + /* 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; + } -#ifdef FLECS_CPP + /* 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; + } + } -/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to - * a uniform identifier */ + if (list) { + if (list->first == match) { + list->first = next; + } + if (list->last == match) { + list->last = prev; + } + } -#define ECS_CONST_PREFIX "const " -#define ECS_STRUCT_PREFIX "struct " -#define ECS_CLASS_PREFIX "class " -#define ECS_ENUM_PREFIX "enum " + match->prev = NULL; + match->next = NULL; -#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)) + query->match_count ++; +} +/* Add node to list */ static -ecs_size_t ecs_cpp_strip_prefix( - char *typeName, - ecs_size_t len, - const char *prefix, - ecs_size_t prefix_len) +void flecs_query_insert_table_node( + ecs_query_t *query, + ecs_query_table_match_t *match) { - 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; + /* 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); } - return len; -} -static -void ecs_cpp_trim_type_name( - char *typeName) -{ - ecs_size_t len = ecs_os_strlen(typeName); + flecs_query_compute_group_id(query, match); - 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); + 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); - while (typeName[len - 1] == ' ' || - typeName[len - 1] == '&' || - typeName[len - 1] == '*') - { - len --; - typeName[len] = '\0'; - } + ecs_query_table_match_t *last = list->last; + ecs_query_table_match_t *last_next = last->next; - /* 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'; + match->prev = last; + match->next = last_next; + last->next = match; + + if (last_next) { + last_next->prev = match; } - 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; + 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; } } - } -} - -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; -} + } else { + ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); -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); + list->first = match; + list->last = match; - 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; + if (query->group_by) { + /* Initialize group with its first node */ + flecs_query_create_group(query, match); } } - symbol_name[i] = '\0'; + if (query->group_by) { + list->info.table_count ++; + list->info.match_count ++; + } - return symbol_name; + 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 -const char* flecs_cpp_func_rchr( - const char *func_name, - ecs_size_t func_name_len, - ecs_size_t func_back_len, - char ch) +ecs_query_table_match_t* flecs_query_cache_add( + ecs_world_t *world, + ecs_query_table_t *elem) { - const char *r = strrchr(func_name, ch); - if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { - return NULL; + 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 r; -} -static -const char* flecs_cpp_func_max( - const char *a, - const char *b) -{ - if (a > b) return a; - return b; + return result; } -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; -} +typedef struct { + ecs_table_t *table; + int32_t column; +} flecs_table_column_t; -// 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) +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) { - ecs_entity_t scope = ecs_get_scope(world); - if (!scope) { - return type_name; - } + 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; - char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); - if (path) { - const char *ptr = strrchr(type_name, ':'); - ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); - if (ptr) { - ptr --; - ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); - ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); - if (name_path_len <= ecs_os_strlen(path)) { - if (!ecs_os_strncmp(type_name, path, name_path_len)) { - type_name = &type_name[name_path_len + 2]; + 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); } } } - ecs_os_free(path); - return type_name; + out->table = table; + out->column = column; } -// 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) +/* 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 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); + 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; } - 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); - } - } + 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 */ } - } else { - /* Ensure that the entity id valid */ - if (!ecs_is_alive(world, id)) { - ecs_ensure(world, id); + + int32_t column = match->columns[field]; + if (column == 0) { + continue; /* Don't track terms that aren't matched */ } - /* 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, "::", "::"); + 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 a component was already registered with this id but with a - * different size, the ecs_component_init function will fail. */ + /* 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; + } + } - /* 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); -} + match->monitor = monitor; -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; + query->flags |= EcsQueryHasMonitor; - /* 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_set_scope(world, prev_scope); + return true; +} - /* 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); - } +/* 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; } - - /* 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); - ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); } - if (existing_out) { - *existing_out = existing; + 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 */ } - return ent; -} + 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; + } -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; + flecs_query_get_column_for_term(query, match, t, &tc); - // 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); - 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); + monitor[field + 1] = flecs_table_get_dirty_state( + filter->world, tc.table)[tc.column + 1]; } } - 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_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; + } + } } - 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; + query->prev_match_count = query->match_count; } -void ecs_cpp_enum_init( - ecs_world_t *world, - ecs_entity_t id) +/* 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) { - (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_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); -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); + 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; + } - 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_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; } - 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); + 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 id; + return monitor[term] != flecs_table_get_dirty_state( + query->filter.world, cur.table)[cur.column + 1]; } -static int32_t flecs_reset_count = 0; +/* 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); -int32_t ecs_cpp_reset_count_get(void) { - return flecs_reset_count; -} + if (flecs_query_get_match_monitor(query, match)) { + return true; + } -int32_t ecs_cpp_reset_count_inc(void) { - return ++flecs_reset_count; -} + 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; + } + } -#endif + 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; + } -/** - * @file addons/alerts.c - * @brief Alerts addon. - */ + 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; + } -#ifdef FLECS_ALERTS + ecs_assert(column < 0, ECS_INTERNAL_ERROR, NULL); + column = -column; -ECS_COMPONENT_DECLARE(FlecsAlerts); + /* 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; + } + } + } -typedef struct EcsAlert { - char *message; - ecs_map_t instances; /* Active instances for metric */ -} EcsAlert; + is_this = ecs_term_match_this(&filter->terms[term_index]); -static -ECS_CTOR(EcsAlert, ptr, { - ptr->message = NULL; - ecs_map_init(&ptr->instances, NULL); -}) + /* 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; + } + } + } + } -static -ECS_DTOR(EcsAlert, ptr, { - ecs_os_free(ptr->message); - ecs_map_fini(&ptr->instances); -}) + 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); -static -ECS_MOVE(EcsAlert, dst, src, { - ecs_os_free(dst->message); - dst->message = src->message; - src->message = NULL; + 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; + } + } + } - ecs_map_fini(&dst->instances); - dst->instances = src->instances; - src->instances = (ecs_map_t){0}; -}) + return false; +} +/* Check if any term for matched table has changed */ static -ECS_CTOR(EcsAlertsActive, ptr, { - ecs_map_init(&ptr->alerts, NULL); -}) +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; -static -ECS_DTOR(EcsAlertsActive, ptr, { - ecs_map_fini(&ptr->alerts); -}) + 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; + } + } + } -static -ECS_MOVE(EcsAlertsActive, dst, src, { - ecs_map_fini(&dst->alerts); - dst->alerts = src->alerts; - src->alerts = (ecs_map_t){0}; -}) + return false; +} 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) +bool flecs_query_check_query_monitor( + ecs_query_t *query) { - EcsAlertsActive *active = ecs_get_mut( - world, source, EcsAlertsActive); - ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); + 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; + } + } + } - 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); + return false; } static -void flecs_alerts_remove_alert_from_src( - ecs_world_t *world, - ecs_entity_t source, - ecs_entity_t alert) +void flecs_query_init_query_monitors( + ecs_query_t *query) { - EcsAlertsActive *active = ecs_get_mut( - world, source, EcsAlertsActive); - ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_remove(&active->alerts, alert); + ecs_query_table_match_t *cur = query->list.first; - if (!ecs_map_count(&active->alerts)) { - ecs_remove(world, source, EcsAlertsActive); - } else { - ecs_modified(world, source, EcsAlertsActive); + /* 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 -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_rule_t *rule = poly[i].poly; - ecs_poly_assert(rule, ecs_rule_t); - - ecs_iter_t rit = ecs_rule_iter(world, rule); - rit.flags |= EcsIterNoData; - rit.flags |= EcsIterIsInstanced; +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); +} - while (ecs_rule_next(&rit)) { - int32_t j, alert_src_count = rit.count; - for (j = 0; j < alert_src_count; j ++) { - ecs_entity_t e = rit.entities[j]; - 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_doc_set_color(world, ai, "#b5494b"); - ecs_defer_suspend(it->world); - flecs_alerts_add_alert_to_src(world, e, a, ai); - ecs_defer_resume(it->world); - aptr[0] = ai; - } - } - } +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 -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); +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; - /* 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; + 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); } - 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_vars_t vars = {0}; - ecs_vars_init(world, &vars); + return qm; +} - 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; +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; - value[i].value += (double)it->delta_system_time; + /* 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); - /* 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 (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; + } - if (ecs_rule_next(&rit)) { - 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; - } + 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; } + } - if (generate_message) { - if (alert_instance[i].message) { - ecs_os_free(alert_instance[i].message); - } + flecs_entity_filter_init(world, &qm->entity_filter, filter, + table, qm->ids, qm->columns); - ecs_iter_to_vars(&rit, &vars, 0); - alert_instance[i].message = ecs_interpolate_string( - world, alert->message, &vars); - } + if (qm->entity_filter) { + query->flags &= ~EcsQueryTrivialIter; + } + if (table->flags & EcsTableHasUnion) { + query->flags &= ~EcsQueryTrivialIter; + } + } - /* Alert instance still matches rule, keep it alive */ - ecs_iter_fini(&rit); + /* 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; } - /* 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); - } + 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); - ecs_vars_fini(&vars); + 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); + } + } + } + } } -ecs_entity_t ecs_alert_init( +static +ecs_query_table_t* flecs_query_table_insert( ecs_world_t *world, - const ecs_alert_desc_t *desc) + ecs_query_t *query, + ecs_table_t *table) { - 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) { - 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; + 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; +} - /* 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); - ecs_modified(world, result, EcsAlert); - - /* Register alert as metric */ - ecs_add(world, result, EcsMetric); - ecs_add_pair(world, result, EcsMetric, EcsCounter); +/** 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; - /* Add severity to alert */ - ecs_entity_t severity = desc->severity; - if (!severity) { - severity = EcsAlertError; - } + 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); - ecs_add_pair(world, result, ecs_id(EcsAlert), severity); + 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); + } - 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 + 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 result; -error: - return 0; } -int32_t ecs_get_alert_count( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t alert) +static +bool flecs_query_match_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) { - 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 (!ecs_map_is_init(&query->cache.index)) { + return false; } - if (alert) { - return ecs_map_get(&active->alerts, alert) != NULL; + 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; } - 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); + 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); - const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); - if (!active) { - return 0; - } + 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_entity_t *ptr = ecs_map_get(&active->alerts, alert); - if (ptr) { - return ptr[0]; + ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); + flecs_query_set_table_match(world, query, qm, table, &it); } -error: - return 0; + return qt != NULL; } -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_set_name_prefix(world, "EcsAlert"); - ECS_COMPONENT_DEFINE(world, EcsAlertInstance); - ECS_COMPONENT_DEFINE(world, EcsAlertsActive); - - 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(EcsAlertsActive), EcsPrivate); - - ecs_struct(world, { - .entity = ecs_id(EcsAlertInstance), - .members = { - { .name = "message", .type = ecs_id(ecs_string_t) } - } - }); +ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static) - ecs_set_hooks(world, EcsAlert, { - .ctor = ecs_ctor(EcsAlert), - .dtor = ecs_dtor(EcsAlert), - .move = ecs_move(EcsAlert) - }); +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; + } - ecs_set_hooks(world, EcsAlertsActive, { - .ctor = ecs_ctor(EcsAlertsActive), - .dtor = ecs_dtor(EcsAlertsActive), - .move = ecs_move(EcsAlertsActive) - }); + int32_t count = flecs_table_data_count(data); + if (count < 2) { + return; + } - ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); - ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, - flecs.metrics.Source, flecs.metrics.Value); + ecs_entity_t *entities = ecs_vec_first(&data->entities); - ecs_system(world, { - .entity = ecs_id(MonitorAlerts), - .no_readonly = true, - .interval = 1.0 - }); + 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); + } - ecs_system(world, { - .entity = ecs_id(MonitorAlertInstances), - .interval = 1.0 - }); + 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); + } } -#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 +/* 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 -ecs_os_thread_t win_thread_new( - ecs_os_thread_callback_t callback, - void *arg) +const void* ptr_from_helper( + sort_helper_t *helper) { - HANDLE *thread = ecs_os_malloc_t(HANDLE); - *thread = CreateThread( - NULL, 0, (LPTHREAD_START_ROUTINE)callback, arg, 0, NULL); - return (ecs_os_thread_t)(uintptr_t)thread; + 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 -void* win_thread_join( - ecs_os_thread_t thr) +ecs_entity_t e_from_helper( + sort_helper_t *helper) { - HANDLE *thread = (HANDLE*)(uintptr_t)thr; - DWORD r = WaitForSingleObject(*thread, INFINITE); - if (r == WAIT_FAILED) { - ecs_err("win_thread_join: WaitForSingleObject failed"); + if (helper->row < helper->count) { + return helper->entities[helper->row]; + } else { + return 0; } - ecs_os_free(thread); - return NULL; } static -ecs_os_thread_id_t win_thread_self(void) +void flecs_query_build_sorted_table_range( + ecs_query_t *query, + ecs_query_table_list_t *list) { - return (ecs_os_thread_id_t)GetCurrentThreadId(); -} + ecs_world_t *world = query->filter.world; + ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, + "cannot sort query in multithreaded mode"); -static -int32_t win_ainc( - int32_t *count) -{ - return InterlockedIncrement(count); -} + 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; + } -static -int32_t win_adec( - int32_t *count) -{ - return InterlockedDecrement(count); -} + 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; -static -int64_t win_lainc( - int64_t *count) -{ - return InterlockedIncrement64(count); -} + 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; -static -int64_t win_ladec( - int64_t *count) -{ - return InterlockedDecrement64(count); -} + ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); -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; -} + 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); -static -void win_mutex_free( - ecs_os_mutex_t m) -{ - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - DeleteCriticalSection(mutex); - ecs_os_free(mutex); -} + 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); + } + } -static -void win_mutex_lock( - ecs_os_mutex_t m) -{ - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - EnterCriticalSection(mutex); -} + 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; + } -static -void win_mutex_unlock( - ecs_os_mutex_t m) -{ - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - LeaveCriticalSection(mutex); -} + 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 ++; + } -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; -} + ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); -static -void win_cond_free( - ecs_os_cond_t c) -{ - (void)c; -} + bool proceed; + do { + int32_t j, min = 0; + proceed = true; -static -void win_cond_signal( - ecs_os_cond_t c) -{ - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeConditionVariable(cond); -} + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } + } -static -void win_cond_broadcast( - ecs_os_cond_t c) -{ - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeAllConditionVariable(cond); -} + if (!proceed) { + break; + } -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); -} + for (j = min + 1; j < to_sort; j++) { + ecs_entity_t e2 = e_from_helper(&helper[j]); + if (!e2) { + continue; + } -static bool win_time_initialized; -static double win_time_freq; -static LARGE_INTEGER win_time_start; + const void *ptr1 = ptr_from_helper(&helper[min]); + const void *ptr2 = ptr_from_helper(&helper[j]); -static -void win_time_setup(void) { - if ( win_time_initialized) { - return; + 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]; } - - win_time_initialized = true; - LARGE_INTEGER freq; - QueryPerformanceFrequency(&freq); - QueryPerformanceCounter(&win_time_start); - win_time_freq = (double)freq.QuadPart / 1000000000.0; + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + + flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } static -void win_sleep( - int32_t sec, - int32_t nanosec) +void flecs_query_build_sorted_tables( + ecs_query_t *query) { - HANDLE timer; - LARGE_INTEGER ft; + ecs_vec_clear(&query->table_slices); - ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + 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); - timer = CreateWaitableTimer(NULL, TRUE, NULL); - SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); -} + /* Sort tables in current group */ + flecs_query_build_sorted_table_range(query, list); -static double win_time_freq; -static ULONG win_current_resolution; + /* Find next group to sort */ + cur = list->last->next; + } while (cur); + } + } else { + flecs_query_build_sorted_table_range(query, &query->list); + } +} static -void win_enable_high_timer_resolution(bool enable) +void flecs_query_sort_tables( + ecs_world_t *world, + ecs_query_t *query) { - HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); - if (!hntdll) { + ecs_order_by_action_t compare = query->order_by; + if (!compare) { return; } - LONG (__stdcall *pNtSetTimerResolution)( - ULONG desired, BOOLEAN set, ULONG * current); + 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 */ - pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) - GetProcAddress(hntdll, "NtSetTimerResolution"); + bool tables_sorted = false; - if(!pNtSetTimerResolution) { - return; - } + 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); - ULONG current, resolution = 10000; /* 1 ms */ + while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { + ecs_table_t *table = qt->hdr.table; + bool dirty = false; - if (!enable && win_current_resolution) { - pNtSetTimerResolution(win_current_resolution, 0, ¤t); - win_current_resolution = 0; - return; - } else if (!enable) { - return; - } + if (flecs_query_check_table_monitor(query, qt, 0)) { + dirty = true; + } - if (resolution == win_current_resolution) { - return; + 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 (win_current_resolution) { - pNtSetTimerResolution(win_current_resolution, 0, ¤t); + if (tables_sorted || query->match_count != query->prev_match_count) { + flecs_query_build_sorted_tables(query); + query->match_count ++; /* Increase version if tables changed */ } +} - if (pNtSetTimerResolution(resolution, 1, ¤t)) { - /* Try setting a lower resolution */ - resolution *= 2; - if(pNtSetTimerResolution(resolution, 1, ¤t)) return; +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; + } } - win_current_resolution = resolution; + return false; } static -uint64_t win_time_now(void) { - uint64_t now; +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; - LARGE_INTEGER qpc_t; - QueryPerformanceCounter(&qpc_t); - now = (uint64_t)(qpc_t.QuadPart / win_time_freq); + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *src = &term->src; - return now; + 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 -void win_fini(void) { - if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { - win_enable_high_timer_resolution(false); +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; } -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); +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; - ecs_os_api_t api = ecs_os_api; + 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; - 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; + 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); - win_time_setup(); + (void)first; + (void)second; + (void)is_src_ok; + (void)is_first_ok; + (void)is_second_ok; - if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { - win_enable_high_timer_resolution(true); - } + /* 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"); - ecs_os_set_api(&api); -} + if (inout != EcsIn && inout != EcsInOutNone) { + /* Non-this terms default to EcsIn */ + if (ecs_term_match_this(term) || inout != EcsInOutDefault) { + query->flags |= EcsQueryHasOutTerms; + } -#else -/** - * @file addons/os_api_impl/posix_impl.inl - * @brief Builtin POSIX implementation for OS API. - */ + bool match_non_this = !ecs_term_match_this(term) || + (term->src.flags & EcsUp); + if (match_non_this && inout != EcsInOutDefault) { + query->flags |= EcsQueryHasNonThisOutTerms; + } + } -#include "pthread.h" + 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; + } + } -#if defined(__APPLE__) && defined(__MACH__) -#include -#elif defined(__EMSCRIPTEN__) -#include -#else -#include -#endif + query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs); -/* 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 + 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 -ecs_os_thread_t posix_thread_new( - ecs_os_thread_callback_t callback, - void *arg) +void flecs_query_update_table( + ecs_query_t *query, + ecs_table_t *table, + bool empty) { - pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); + 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 (pthread_create (thread, NULL, callback, arg) != 0) { - ecs_os_abort(); + 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); + } + } } - return (ecs_os_thread_t)(uintptr_t)thread; + ecs_assert(cur_count || query->list.first == NULL, + ECS_INTERNAL_ERROR, NULL); } static -void* posix_thread_join( - ecs_os_thread_t thread) +void flecs_query_add_subquery( + ecs_world_t *world, + ecs_query_t *parent, + ecs_query_t *subquery) { - void *arg; - pthread_t *thr = (pthread_t*)(uintptr_t)thread; - pthread_join(*thr, &arg); - ecs_os_free(thr); - return arg; + 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 -ecs_os_thread_id_t posix_thread_self(void) +void flecs_query_notify_subqueries( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) { - return (ecs_os_thread_id_t)pthread_self(); + 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 -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(); +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); } - return value; -#endif } static -int32_t posix_adec( - int32_t *count) +void flecs_query_table_free( + ecs_query_t *query, + ecs_query_table_t *elem) { - 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 + flecs_query_table_match_free(query, elem, elem->first); + flecs_bfree(&query->filter.world->allocators.query_table, elem); } static -int64_t posix_lainc( - int64_t *count) +void flecs_query_unmatch_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_query_table_t *elem) { - int64_t value; -#ifdef __GNUC__ - value = __sync_add_and_fetch (count, 1); - return value; -#else - if (pthread_mutex_lock(&atomic_mutex)) { - abort(); + if (!elem) { + elem = ecs_table_cache_get(&query->cache, table); } - value = (*count) += 1; - if (pthread_mutex_unlock(&atomic_mutex)) { - abort(); + if (elem) { + ecs_table_cache_remove(&query->cache, elem->table_id, &elem->hdr); + flecs_query_table_free(query, elem); } - return value; -#endif } +/* Rematch system with tables after a change happened to a watched entity */ static -int64_t posix_ladec( - int64_t *count) +void flecs_query_rematch_tables( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_t *parent_query) { - int64_t value; -#ifdef __GNUC__ - value = __sync_sub_and_fetch (count, 1); - return value; -#else - if (pthread_mutex_lock(&atomic_mutex)) { - abort(); + 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; } - value = (*count) -= 1; - if (pthread_mutex_unlock(&atomic_mutex)) { - abort(); + + 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); } - 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(); + 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); } - 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); + 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 posix_mutex_lock( - ecs_os_mutex_t m) +void flecs_query_remove_subquery( + ecs_query_t *parent, + ecs_query_t *sub) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_lock(mutex)) { - abort(); + 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); } -static -void posix_mutex_unlock( - ecs_os_mutex_t m) +/* -- Private API -- */ + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_unlock(mutex)) { - abort(); + 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; } -} -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(); + if (notify) { + flecs_query_notify_subqueries(world, query, event); } - return (ecs_os_cond_t)(uintptr_t)cond; } -static -void posix_cond_free( - ecs_os_cond_t c) +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) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_destroy(cond)) { - abort(); + 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"); } - ecs_os_free(cond); + + 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 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 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; } -static -void posix_cond_broadcast( - ecs_os_cond_t c) +/* 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) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_broadcast(cond)) { - abort(); + 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 posix_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) +static +void flecs_query_on_event( + ecs_iter_t *it) { - 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(); + /* 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; } -} -static bool posix_time_initialized; + ecs_query_t *query = o->ctx; + ecs_table_t *table = it->table; + ecs_entity_t event = it->event; -#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 + 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; + } -static -void posix_time_setup(void) { - if (posix_time_initialized) { + 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; } - - 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 + 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 posix_sleep( - int32_t sec, - int32_t nanosec) +void flecs_query_table_cache_free( + ecs_query_t *query) { - struct timespec sleepTime; - ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_iter_t it; + ecs_query_table_t *qt; - sleepTime.tv_sec = sec; - sleepTime.tv_nsec = nanosec; - if (nanosleep(&sleepTime, NULL)) { - ecs_err("nanosleep failed"); + 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); } -/* 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; +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)); + } } -#endif static -uint64_t posix_time_now(void) { - ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); +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); + } +} - uint64_t now; +static +void flecs_query_fini( + ecs_query_t *query) +{ + ecs_world_t *world = query->filter.world; - #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 + 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; + } -/** - * @file addons/plecs.c - * @brief Plecs addon. - */ + 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); + } -#ifdef FLECS_PLECS + flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryOrphan + }); -ECS_COMPONENT_DECLARE(EcsScript); + flecs_query_for_each_component_monitor(world, query, + flecs_monitor_unregister); + flecs_query_table_cache_free(query); -#include + ecs_map_fini(&query->groups); -#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" + 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); -#define STACK_MAX_SIZE (64) + flecs_query_allocators_fini(query); -typedef struct { - ecs_value_t value; - bool owned; -} plecs_with_value_t; + if (query->ctx_free) { + query->ctx_free(query->ctx); + } + if (query->binding_ctx_free) { + query->binding_ctx_free(query->binding_ctx); + } -typedef struct { - const char *name; - const char *code; + ecs_poly_free(query, ecs_query_t); +} - ecs_entity_t last_predicate; - ecs_entity_t last_subject; - ecs_entity_t last_object; +/* -- Public API -- */ - ecs_id_t last_assign_id; - ecs_entity_t assign_to; +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_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; + 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; - char *annot[STACK_MAX_SIZE]; - int32_t annot_count; + observer_desc.filter.flags = EcsFilterMatchEmptyTables; + observer_desc.filter.storage = &result->filter; + result->filter = ECS_FILTER_INIT; - ecs_vars_t vars; - char var_name[256]; - ecs_entity_t var_type; + if (ecs_filter_init(world, &observer_desc.filter) == NULL) { + goto error; + } - 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; + ECS_BIT_COND(result->flags, EcsQueryTrivialIter, + !!(result->filter.flags & EcsFilterMatchOnlyThis)); - int32_t errors; -} plecs_state_t; + flecs_query_allocators_init(result); -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); + 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; -static void flecs_dtor_script(EcsScript *ptr) { - ecs_os_free(ptr->script); - ecs_vec_fini_t(NULL, &ptr->using_, ecs_entity_t); + /* 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 */ - 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); + entity = ecs_observer_init(world, &observer_desc); + if (!entity) { + goto error; + } } - 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); -}) + result->iterable.init = flecs_query_iter_init; + result->dtor = (ecs_poly_dtor_t)flecs_query_fini; + result->prev_match_count = -1; -/* 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); + 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 (!st) { - ecs_err("assembly '%s' is not a struct, cannot construct", ti->name); - return; + 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); } - const EcsScript *script = ecs_get(world, assembly, EcsScript); - if (!script) { - ecs_err("assembly '%s' is not a script, cannot construct", ti->name); - return; - } + ecs_log_push_1(); - 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; + if (flecs_query_process_signature(world, result)) { + goto error; } - 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; - } + /* 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]; + } - 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); - } + 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; } -} -/* 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; + if (desc->parent != NULL) { + result->flags |= EcsQueryIsSubquery; } - 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); + /* 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; - 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; + 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); + } + } } - const EcsStruct *st = ecs_record_get(world, r, EcsStruct); - if (!st) { - ecs_err("assembly '%s' is not a struct", name); - return; + if (!entity) { + entity = ecs_new_id(world); } - const EcsScript *script = ecs_record_get(world, r, EcsScript); - if (!script) { - ecs_err("assembly '%s' is missing a script", name); - return; + 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; - void *data = ecs_field_w_size(it, flecs_ito(size_t, ct->size), 1); + /* 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); - 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); + ecs_table_cache_init(world, &result->cache); - /* 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]; + if (!desc->parent) { + flecs_query_match_tables(world, result); + } else { + flecs_query_add_subquery(world, desc->parent, result); + result->parent = desc->parent; + } - 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; - } + if (desc->order_by) { + flecs_query_order_by( + world, result, desc->order_by_component, desc->order_by, + desc->sort_table); + } - /* Assign assembly property from assembly instance */ - var->value.type = member->type; - var->value.ptr = ECS_OFFSET(data, member->offset); - var->owned = false; - } + if (!ecs_query_table_count(result) && result->filter.term_count) { + ecs_add_id(world, entity, EcsEmpty); + } - /* Update script with new code/properties */ - ecs_entity_t instance = it->entities[i]; - ecs_script_update(world, assembly, instance, script->script, &vars); - ecs_vars_fini(&vars); + ecs_poly_modified(world, entity, ecs_query_t); - if (ecs_record_has_id(world, r, EcsFlatten)) { - ecs_flatten(it->real_world, ecs_childof(instance), NULL); - } + ecs_log_pop_1(); - data = ECS_OFFSET(data, ct->size); + return result; +error: + if (result) { + ecs_filter_fini(&result->filter); + ecs_os_free(result); } + return NULL; } -/* Delete contents of assembly instance */ -static -void flecs_assembly_on_remove( - ecs_iter_t *it) +void ecs_query_fini( + ecs_query_t *query) { - int32_t i; - for (i = 0; i < it->count; i ++) { - ecs_entity_t instance = it->entities[i]; - ecs_script_clear(it->world, 0, instance); - } + ecs_poly_assert(query, ecs_query_t); + ecs_delete(query->filter.world, query->filter.entity); } -/* 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 ecs_filter_t* ecs_query_get_filter( + const ecs_query_t *query) { - 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); + ecs_poly_assert(query, ecs_query_t); + return &query->filter; +} - 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; - } +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"); - 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_var_t *var = &it->variables[0]; + ecs_table_t *table = var->range.table; + if (!table) { + goto nodata; + } - 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 */ + 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; } - return 0; + 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; } -/* 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) +ecs_iter_t ecs_query_iter( + const ecs_world_t *stage, + ecs_query_t *query) { - 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); + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); - 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_world_t *world = query->filter.world; + ecs_poly_assert(world, ecs_world_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; - } + /* Process table events to ensure that the list of iterated tables doesn't + * contain empty tables. */ + flecs_process_pending_tables(world); - 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 query has order_by, apply sort */ + flecs_query_sort_tables(world, query); - if (flecs_assembly_init_defaults( - world, name, expr, ptr, assembly, script, state)) - { - return -1; + /* If monitors changed, do query rematching */ + if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(world); } - ecs_modified(world, assembly, EcsScript); + /* Prepare iterator */ - 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; -} + 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); + } -/* Parser */ + ecs_query_iter_t it = { + .query = query, + .node = query->list.first, + .last = NULL + }; -static -bool plecs_is_newline_comment( - const char *ptr) -{ - if (ptr[0] == '/' && ptr[1] == '/') { - return true; + if (query->order_by && query->list.info.table_count) { + it.node = ecs_vec_first(&query->table_slices); } - return false; -} -static -const char* plecs_parse_fluff( - const char *ptr) -{ - do { - /* Skip whitespaces before checking for a comment */ - ptr = ecs_parse_ws(ptr); + ecs_iter_t result = { + .real_world = world, + .world = ECS_CONST_CAST(ecs_world_t*, stage), + .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 + }; - /* Newline comment, skip until newline character */ - if (plecs_is_newline_comment(ptr)) { - ptr += 2; + flecs_filter_apply_iter_flags(&result, &query->filter); - while (ptr[0] && ptr[0] != TOK_NEWLINE) { - ptr ++; + 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; } } - /* If a newline character is found, skip it */ - if (ptr[0] == TOK_NEWLINE) { - ptr ++; + 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); + } - } while (isspace(ptr[0]) || plecs_is_newline_comment(ptr)); + result.sizes = query->filter.sizes; - return ptr; + return result; +error: +noresults: + result.priv.iter.query.node = NULL; + return result; } -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) +void ecs_query_set_group( + ecs_iter_t *it, + uint64_t group_id) { - ecs_entity_t e = 0; + 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); - 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; - } - } - } + ecs_query_iter_t *qit = &it->priv.iter.query; + ecs_query_t *q = qit->query; + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - if (!e) { - e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); + ecs_query_table_list_t *node = flecs_query_get_group(q, group_id); + if (!node) { + qit->node = NULL; + return; } - return e; + 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; } -/* 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) +const ecs_query_group_info_t* ecs_query_get_group_info( + const ecs_query_t *query, + uint64_t group_id) { - return plecs_lookup(world, path, ctx, 0, false); + ecs_query_table_list_t *node = flecs_query_get_group(query, group_id); + if (!node) { + return NULL; + } + + return &node->info; } -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) +void* ecs_query_get_group_ctx( + const ecs_query_t *query, + uint64_t group_id) { - 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; + const ecs_query_group_info_t *info = + ecs_query_get_group_info(query, group_id); + if (!info) { + return NULL; + } else { + return info->ctx; } +} - if (!e) { - e = plecs_lookup(world, path, state, rel, is_subject); - } +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; - 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; - } + 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; + } - 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); - } + flecs_table_column_t tc; + flecs_query_get_column_for_term(query, qm, i, &tc); - e = ecs_add_path(world, e, 0, path); - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + if (tc.column == -1) { + continue; + } - 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_assert(tc.table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *dirty_state = tc.table->dirty_state; + if (!dirty_state) { + continue; } - ecs_entity_t with = ecs_get_with(world); - if (with) { - ecs_add_id(world, e, with); + if (table != tc.table) { + if (inout == EcsInOutDefault) { + continue; + } } - } - } - if (is_new) { - if (state->assembly && !state->assembly_instance) { - ecs_add_id(world, e, EcsPrefab); - } + ecs_assert(tc.column >= 0, ECS_INTERNAL_ERROR, NULL); - if (state->global_with) { - ecs_add_id(world, e, state->global_with); + dirty_state[tc.column + 1] ++; } } - - return e; } -static -bool plecs_pred_is_subj( - ecs_term_t *term, - plecs_state_t *state) +bool ecs_query_next_table( + ecs_iter_t *it) { - 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; + 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 (state->decl_type) { - return false; + + 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; } - return true; +error: + query->match_count = query->prev_match_count; + ecs_iter_fini(it); + return false; } -/* 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"; +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; } - return NULL; -} -static -char* plecs_trim_annot( - char *annot) -{ - annot = (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; -} + 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); -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); + 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); } -#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) +int ecs_query_populate( + ecs_iter_t *it, + bool when_changed) { - state->last_subject = 0; - state->last_predicate = 0; - state->last_object = 0; - state->last_assign_id = 0; - - const char *pred_name = term->first.name; - const char *subj_name = term->src.name; - const char *obj_name = term->second.name; + 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); - if (!subj_name) { - subj_name = plecs_set_mask_to_name(term->src.flags); - } - if (!obj_name) { - obj_name = plecs_set_mask_to_name(term->second.flags); + 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; } - if (!ecs_term_id_is_set(&term->first)) { - ecs_parser_error(name, expr, column, "missing predicate in expression"); - return -1; - } + 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; - if (state->assign_stmt && !ecs_term_match_this(term)) { - ecs_parser_error(name, expr, column, - "invalid statement in assign statement"); - return -1; - } +repeat: + result = EcsIterNextYield; - bool pred_as_subj = plecs_pred_is_subj(term, state); - ecs_entity_t pred = plecs_ensure_entity(world, state, pred_name, 0, pred_as_subj); - ecs_entity_t subj = plecs_ensure_entity(world, state, subj_name, pred, true); - ecs_entity_t obj = 0; + ecs_os_memcpy_n(it->sources, match->sources, ecs_entity_t, + filter->field_count); - if (ecs_term_id_is_set(&term->second)) { - obj = plecs_ensure_entity(world, state, obj_name, pred, - !state->assign_stmt && !state->with_stmt); - if (!obj) { - return -1; + 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; } - } - if (state->assign_stmt || state->isa_stmt) { - subj = state->assign_to; + it->ids[field] = match->ids[field]; + it->columns[field] = match->columns[field]; } - if (state->isa_stmt && obj) { - ecs_parser_error(name, expr, column, - "invalid object in inheritance statement"); - return -1; - } + 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 (state->isa_stmt) { - pred = ecs_pair(EcsIsA, pred); - } + 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; + } + } - if (subj == EcsVariable) { - subj = pred; + it->group_id = match->group_id; + } else { + range->offset = 0; + range->count = 0; } - if (subj) { - if (!obj) { - ecs_add_id(world, subj, pred); - state->last_assign_id = pred; - } else { - ecs_add_pair(world, subj, pred, obj); - state->last_object = obj; - state->last_assign_id = ecs_pair(pred, obj); + if (when_changed) { + if (!ecs_query_changed(NULL, it)) { + if (result == EcsIterYield) { + goto repeat; + } else { + result = EcsIterNext; + goto done; + } } - state->last_predicate = pred; - state->last_subject = subj; + } - pred_as_subj = false; - } else { - if (!obj) { - /* If no subject or object were provided, use predicate as subj - * unless the expression explictly 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) { - 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, subj, id); - ecs_value_copy(world, v->value.type, ptr, v->value.ptr); - ecs_modified_id(world, subj, id); - } else { - ecs_add_id(world, subj, id); - } - } - } - } - - /* 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; - } + it->references = ecs_vec_first(&match->refs); + it->instance_count = 0; - plecs_apply_annotations(world, subj, state); - } + flecs_iter_populate_data(world, it, table, range->offset, range->count, + it->ptrs); - return 0; +error: +done: + return result; } -static -const char* plecs_parse_inherit_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +bool ecs_query_next( + ecs_iter_t *it) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "cannot nest inheritance"); - return NULL; - } + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - if (!state->last_subject) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign inheritance to"); - return NULL; + if (flecs_iter_next_row(it)) { + return true; } - - state->isa_stmt = true; - state->assign_to = state->last_subject; - return ptr; + return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); +error: + return false; } -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) +bool ecs_query_next_instanced( + ecs_iter_t *it) { - ecs_value_t value = {0}; + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - 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); + 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); } } - 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); + 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; } - goto error; + iter->node = cur->next; + iter->prev = cur; + flecs_query_populate_trivial(it, cur); + return true; } - 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; + /* 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); } } - state->var_is_prop = false; - return ptr; -error: - return 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; } -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) +bool ecs_query_changed( + ecs_query_t *query, + const ecs_iter_t *it) { - (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; - } + 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_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_query_table_match_t *qm = + (ecs_query_table_match_t*)it->priv.iter.query.prev; + ecs_assert(qm != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t assign_to = state->assign_to; - if (!assign_to) { - assign_to = state->last_subject; - } + if (!query) { + query = it->priv.iter.query.query; + } else { + ecs_check(query == it->priv.iter.query.query, + ECS_INVALID_PARAMETER, NULL); + } - if (!assign_to) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign to"); - return NULL; - } + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(query, ecs_query_t); - 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; + return flecs_query_check_match_monitor(query, qm, it); } - if (assign_to == EcsVariable) { - assign_to = type; - } + ecs_poly_assert(query, ecs_query_t); + ecs_check(!(query->flags & EcsQueryIsOrphaned), + ECS_INVALID_PARAMETER, NULL); - void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); + flecs_process_pending_tables(query->filter.world); - 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; + if (!(query->flags & EcsQueryHasMonitor)) { + query->flags |= EcsQueryHasMonitor; + flecs_query_init_query_monitors(query); + return true; /* Monitors didn't exist yet */ } - ecs_modified_id(world, assign_to, assign_id); + if (query->match_count != query->prev_match_count) { + return true; + } - return ptr; + return flecs_query_check_query_monitor(query); +error: + return false; } -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 ecs_query_skip( + ecs_iter_t *it) { - (void)world; - - state->isa_stmt = false; + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); - /* 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; + 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; } - 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; + } else { + it->priv.iter.query.prev = NULL; } +} - state->assign_stmt = true; - - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - ecs_entity_t type = 0; +bool ecs_query_orphaned( + const ecs_query_t *query) +{ + ecs_poly_assert(query, ecs_query_t); + return query->flags & EcsQueryIsOrphaned; +} - /* 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; - } - } +char* ecs_query_str( + const ecs_query_t *query) +{ + return ecs_filter_str(query->filter.world, &query->filter); +} - /* 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]; - } +int32_t ecs_query_table_count( + const ecs_query_t *query) +{ + ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); + return query->cache.tables.count; +} - /* 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]; - } - } +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; +} - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "missing type for assignment"); - return NULL; - } +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; + } - state->last_assign_id = type; + for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) { + result += ecs_table_count(cur->table); } - return ptr; + return result; } -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) +void* ecs_query_get_ctx( + const ecs_query_t *query) { - 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; - } + return query->ctx; +} - 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->type_info; - 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; - } +void* ecs_query_get_binding_ctx( + const ecs_query_t *query) +{ + return query->binding_ctx; +} - 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; - } +/** + * @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. + */ - 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; + +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 ptr; + return -1; } static -const char* plecs_parse_assign_with_var( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +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(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_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); - 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; + 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; + } } - 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; + return -1; } 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; +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; } - - 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 (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; +} - if (!state->assign_to) { - ecs_parser_error(name, expr, ptr - expr, - "missing lvalue for variable assignment '%s'", var_name); - return NULL; - } +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; - /* 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; + 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; + } + } } - 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; - } + 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 (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; - } + if (!flecs_type_can_inherit_id(world, table, idr, id)) { + return -1; + } + } - ecs_modified_id(world, assign_to, type); + if (!idr_r) { + idr_r = flecs_id_record_get(world, rel); + if (!idr_r) { + return -1; + } + } - return ptr; -} + 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); -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; + 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); } } - state->using[state->using_frame ++] = scope; + return -1; } -static -const char* plecs_parse_using_stmt( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +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 (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; - } + if (!table) return -1; - 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; - } + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - /* Lookahead as * is not matched by parse_token */ - if (ptr[0] == '*') { - using_path[len] = '*'; - using_path[len + 1] = '\0'; - len ++; - ptr ++; - } + flags = flags ? flags : (EcsSelf|EcsUp); - 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; + if (!idr) { + idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; } + } - /* 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; + 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); } - - plecs_push_using(scope, state); } - state->using_frames[state->sp] = state->using_frame; - return ptr; + 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; } -static -const char* plecs_parse_module_stmt( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +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) { - 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; - } + if (!table) return -1; - 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_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_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; + ecs_id_record_t *idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; } - state->is_module = true; - state->sp ++; - state->scope[state->sp] = module; - ecs_set_scope(world, module); - return ptr; + 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; } -static -const char* plecs_parse_with_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +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 (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with after inheritance"); - return NULL; - } + if (!table) return -1; - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with in assign_stmt"); - return NULL; - } + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + (void)world; - /* Add following expressions to with list */ - state->with_stmt = true; - return ptr + 5; + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + return flecs_type_search(table, id, idr, ids, id_out, 0); } -static -const char* plecs_parse_assembly_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with after inheritance"); - return NULL; - } + if (!table) return -1; - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with in assign_stmt"); - return NULL; + 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; } - state->assembly_stmt = true; - return ptr + 9; + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + return flecs_type_search(table, id, idr, ids, id_out, 0); } -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) +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) { - 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; + if (!offset) { + ecs_poly_assert(world, ecs_world_t); + return ecs_search(world, table, id, id_out); } - *type_out = prop_type; + if (!table) return -1; - return ptr; + 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 -const char* plecs_parse_const_stmt( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +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) { - ptr = ecs_parse_token(name, expr, ptr + 5, state->var_name, 0); - if (!ptr) { - return NULL; + int32_t result = 0; + + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return 0; } - ptr = ecs_parse_ws(ptr); + 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); - if (ptr[0] == ':') { - ptr = plecs_parse_var_type( - world, name, expr, ptr, state, &state->last_assign_id); - if (!ptr) { - return 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; } - - 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; + + return result + 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) +int32_t flecs_relation_depth( + const ecs_world_t *world, + ecs_entity_t r, + const ecs_table_t *table) { - char prop_name[ECS_MAX_TOKEN_SIZE]; - ptr = ecs_parse_token(name, expr, ptr + 5, prop_name, 0); - if (!ptr) { - return NULL; + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); + if (!idr) { + return 0; } - ptr = ecs_parse_ws(ptr); + 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; + } - if (ptr[0] != ':') { - ecs_parser_error(name, expr, ptr - expr, - "expected ':' after prop declaration"); - return NULL; + 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]); + } } - ecs_entity_t prop_type; - ptr = plecs_parse_var_type(world, name, expr, ptr, state, &prop_type); - if (!ptr) { - return NULL; - } + return flecs_relation_depth_walk(world, idr, table, table) + depth_offset; +} - 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; - } +/** + * @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. + */ - 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 +ecs_cmd_t* flecs_cmd_alloc( + ecs_stage_t *stage) +{ + ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->commands, + ecs_cmd_t); + ecs_os_zeromem(cmd); + return cmd; } static -const char* plecs_parse_scope_open( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +ecs_cmd_t* flecs_cmd_new( + ecs_stage_t *stage, + ecs_entity_t e, + bool is_delete, + bool can_batch) { - 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; - - 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); + if (e) { + ecs_vec_t *cmds = &stage->commands; + ecs_cmd_entry_t *entry = flecs_sparse_try_t( + &stage->cmd_entries, ecs_cmd_entry_t, e); + int32_t cur = ecs_vec_count(cmds); + if (entry) { + int32_t last = entry->last; + if (entry->last == -1) { + /* Entity was deleted, don't insert command */ + return NULL; } - } 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); + + if (can_batch) { + 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; } - ecs_set_scope(world, state->last_predicate); } + } else if (can_batch || is_delete) { + entry = flecs_sparse_ensure_t(&stage->cmd_entries, + ecs_cmd_entry_t, e); + entry->first = cur; } - - state->scope[state->sp] = scope; - state->default_scope_type[state->sp] = default_scope_type; - - if (state->assembly_stmt) { - 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; + if (can_batch) { + entry->last = cur; + } + if (is_delete) { + /* Prevent insertion of more commands for entity */ + entry->last = -1; } - } else { - state->scope[state->sp] = state->scope[state->sp - 1]; - state->default_scope_type[state->sp] = - state->default_scope_type[state->sp - 1]; } - 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); - - return ptr; + return flecs_cmd_alloc(stage); } static -const char* plecs_parse_scope_close( +void flecs_stages_merge( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + bool force_merge) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid '}' after inheritance statement"); - return NULL; - } + bool is_stage = ecs_poly_is(world, ecs_stage_t); + ecs_stage_t *stage = flecs_stage_from_world(&world); - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "unfinished assignment before }"); - return NULL; - } + bool measure_frame_time = ECS_BIT_IS_SET(world->flags, + EcsWorldMeasureFrameTime); - 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; + ecs_time_t t_start = {0}; + if (measure_frame_time) { + ecs_os_get_time(&t_start); } - 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; + 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); } - } 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; - } + flecs_eval_component_monitors(world); - ecs_id_t id = state->scope[state->sp]; - if (!id || ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_set_with(world, id); + if (measure_frame_time) { + world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } - if (!id || !ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_set_scope(world, id); - } + world->info.merge_count_total ++; - 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; - } + /* If stage is asynchronous, deferring is always enabled */ + if (stage->async) { + flecs_defer_begin(world, stage); } + + ecs_log_pop_3(); +} - 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 +void flecs_stage_auto_merge( + ecs_world_t *world) +{ + flecs_stages_merge(world, false); } static -const char *plecs_parse_plecs_term( +void flecs_stage_manual_merge( + ecs_world_t *world) +{ + flecs_stages_merge(world, true); +} + +bool flecs_defer_begin( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_stage_t *stage) { - ecs_term_t term = {0}; - ecs_entity_t decl_id = 0; - if (state->decl_stmt) { - decl_id = state->last_predicate; - } + 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; +} - ptr = ecs_parse_term(world, name, expr, ptr, &term); - if (!ptr) { - return NULL; +bool flecs_defer_cmd( + ecs_stage_t *stage) +{ + if (stage->defer) { + return (stage->defer > 0); } - if (flecs_isident(ptr[0])) { - state->decl_type = true; - } + stage->defer ++; + return false; +} - if (!ecs_term_is_initialized(&term)) { - ecs_parser_error(name, expr, ptr - expr, "expected identifier"); - return NULL; /* No term found */ +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(stage, entity, false, true); + if (cmd) { + cmd->kind = EcsOpModified; + cmd->id = id; + cmd->entity = entity; + } + return true; } + return false; +} - if (plecs_create_term(world, &term, name, expr, (ptr - expr), state)) { - ecs_term_fini(&term); - return NULL; /* Failed to create term */ +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, entity, false, false); + if (cmd) { + cmd->kind = EcsOpClone; + cmd->id = src; + cmd->entity = entity; + cmd->is._1.clone_value = clone_value; + } + return true; } + return false; +} - if (decl_id && state->last_subject) { - ecs_add_id(world, state->last_subject, decl_id); +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, entity, false, false); + if (cmd) { + cmd->kind = EcsOpPath; + cmd->entity = entity; + cmd->id = parent; + cmd->is._1.value = ecs_os_strdup(name); + } + return true; } + return false; +} - state->decl_type = 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, entity, true, false); + if (cmd) { + cmd->kind = EcsOpDelete; + cmd->entity = entity; + } + return true; + } + return false; +} - ecs_term_fini(&term); +bool flecs_defer_clear( + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); + if (cmd) { + cmd->kind = EcsOpClear; + cmd->entity = entity; + } + return true; + } + return false; +} - return ptr; +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_alloc(stage); + cmd->kind = EcsOpOnDeleteAction; + cmd->id = id; + cmd->entity = action; + return true; + } + return false; } -static -const char* plecs_parse_annotation( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +bool flecs_defer_enable( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + bool enable) { - do { - if(state->annot_count >= STACK_MAX_SIZE) { - ecs_parser_error(name, expr, ptr - expr, - "max number of annotations reached"); - return NULL; + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); + if (cmd) { + cmd->kind = enable ? EcsOpEnable : EcsOpDisable; + cmd->entity = entity; + cmd->id = id; } + return true; + } + return false; +} - char ch; - const char *start = ptr; - for (; (ch = *ptr) && ch != '\n'; ptr ++) { } +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)); - 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'; + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new_id(world); + } - state->annot[state->annot_count] = annot; - state->annot_count ++; + *ids_out = ids; - ptr = plecs_parse_fluff(ptr); - } while (ptr[0] == '@'); + /* Store data in op */ + ecs_cmd_t *cmd = flecs_cmd_alloc(stage); + if (cmd) { + cmd->kind = EcsOpBulkNew; + cmd->id = id; + cmd->is._n.entities = ids; + cmd->is._n.count = count; + } - return ptr; + return true; + } + return false; } -static -void plecs_clear_annotations( - plecs_state_t *state) +bool flecs_defer_add( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) { - int32_t i, count = state->annot_count; - for (i = 0; i < count; i ++) { - ecs_os_free(state->annot[i]); + if (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); + if (cmd) { + cmd->kind = EcsOpAdd; + cmd->id = id; + cmd->entity = entity; + } + return true; } - state->annot_count = 0; + return false; } -static -const char* plecs_parse_stmt( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +bool flecs_defer_remove( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) { - 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 (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); + if (cmd) { + cmd->kind = EcsOpRemove; + cmd->id = id; + cmd->entity = entity; + } + return true; } + return false; +} - 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; +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, + bool need_value) +{ + ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); + if (!cmd) { + if (need_value) { + /* Entity is deleted by a previous command, but we still need to + * return a temporary storage to the application. */ + cmd_kind = EcsOpSkip; + } else { + /* No value needs to be returned, we can drop the command */ + return NULL; } - } 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; + /* 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; } - ptr = tptr; - goto decl_stmt; + } else { + ti = idr->type_info; } -next_term: - ptr = plecs_parse_fluff(ptr); + /* If the id isn't associated with a type, we can't set anything */ + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - 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] == '{') { - 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; - } + /* Make sure the size of the value equals the type size */ + ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); + size = ti->size; - goto next_term; - } else { - ptr = plecs_parse_fluff(ptr + 1); - goto scope_open; + /* 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)); + } } } - 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; + /* Get existing value from storage */ + void *cmd_value = existing; + bool emplace = cmd_kind == EcsOpEmplace; - /* Expect base identifier */ - goto term_expr; + /* 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->defer_stack; + cmd_value = flecs_stack_alloc(stack, size, ti->alignment); -assign_stmt: - ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); - if (!ptr) goto error; + /* 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 */ - ptr = plecs_parse_fluff(ptr); + /* Check if entity inherits component */ + void *base = NULL; + if (table && (table->flags & EcsTableHasIsA)) { + base = flecs_get_base_component(world, table, id, idr, 0); + } - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - goto assign_expr; + 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); + } } - /* Expect component identifiers */ - goto term_expr; - -assign_expr: - ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, NULL); - if (!ptr) goto error; + 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_alloc(stage); + } - 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; - } + 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 == EcsOpSet) { + cmd->kind = EcsOpAddModified; + } else { + cmd->kind = EcsOpAdd; } - ecs_parser_error(name, expr, (ptr - expr), - "unexpected '{' after assignment"); - goto error; + cmd->id = id; + cmd->entity = entity; } - state->assign_stmt = false; - state->assign_to = 0; - goto done; + return cmd_value; +error: + return NULL; +} -assign_var_as_component: { - ptr = plecs_parse_var_as_component(world, name, expr, ptr, state); - if (!ptr) { - goto error; +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); } - state->assign_stmt = false; - state->assign_to = 0; - goto done; + + ecs_vec_clear(&stage->post_frame_actions); } -scope_open: - ptr = plecs_parse_scope_open(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; +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); -scope_close: - ptr = plecs_parse_scope_close(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; + stage->world = world; + stage->thread_ctx = world; + stage->auto_merge = true; + stage->async = false; -done: - return ptr; -error: - return NULL; + flecs_stack_init(&stage->defer_stack); + 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->commands, ecs_cmd_t, 0); + ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); + flecs_sparse_init_t(&stage->cmd_entries, &stage->allocator, + &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); } -static -int flecs_plecs_parse( +void flecs_stage_fini( ecs_world_t *world, - const char *name, - const char *expr, - ecs_vars_t *vars, - ecs_entity_t script, - ecs_entity_t instance) + ecs_stage_t *stage) { - const char *ptr = expr; - ecs_term_t term = {0}; - plecs_state_t state = {0}; - - if (!expr) { - return 0; - } + (void)world; + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); - state.scope[0] = 0; - ecs_entity_t prev_scope = ecs_set_scope(world, 0); - ecs_entity_t prev_with = ecs_set_with(world, 0); + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vec_count(&stage->commands) == 0, ECS_INTERNAL_ERROR, NULL); - 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_poly_fini(stage, ecs_stage_t); - ecs_vars_init(world, &state.vars); + flecs_sparse_fini(&stage->cmd_entries); - 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; + ecs_allocator_t *a = &stage->allocator; + ecs_vec_fini_t(a, &stage->commands, ecs_cmd_t); + 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); + flecs_stack_fini(&stage->defer_stack); + 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); +} - 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; - } +void ecs_set_stage_count( + ecs_world_t *world, + int32_t stage_count) +{ + ecs_poly_assert(world, ecs_world_t); - if (instance) { - ecs_set_scope(world, instance); - } - } - } + /* World must have at least one default stage */ + ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), + ECS_INTERNAL_ERROR, NULL); - if (vars) { - state.vars.root.parent = vars->cur; + 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; } - do { - expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); - if (!ptr) { - goto error; - } + int32_t i, count = world->stage_count; + if (count && count != stage_count) { + ecs_stage_t *stages = world->stages; - if (!ptr[0]) { - break; /* End of expression */ + 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]); } - } while (true); - - ecs_set_scope(world, prev_scope); - ecs_set_with(world, prev_with); - plecs_clear_annotations(&state); - if (state.is_module) { - state.sp --; + ecs_os_free(world->stages); } - if (state.sp != 0) { - ecs_parser_error(name, expr, 0, "missing end of scope"); - goto error; - } + if (stage_count) { + world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count); - if (state.assign_stmt) { - ecs_parser_error(name, expr, 0, "unfinished assignment"); - goto error; - } + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = &world->stages[i]; + flecs_stage_init(world, stage); + stage->id = i; - if (state.errors) { - goto error; + /* 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; } - ecs_vars_fini(&state.vars); + /* 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; + } - return 0; + world->stage_count = stage_count; error: - ecs_vars_fini(&state.vars); - ecs_set_scope(world, state.scope[0]); - ecs_set_with(world, prev_with); - ecs_term_fini(&term); - return -1; + return; } -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); +int32_t ecs_get_stage_count( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return world->stage_count; } -static -char* flecs_load_from_file( - const char *filename) +int32_t ecs_get_stage_id( + const ecs_world_t *world) { - 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; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - /* Determine file size */ - fseek(file, 0 , SEEK_END); - bytes = (int32_t)ftell(file); - if (bytes == -1) { - goto error; - } - rewind(file); + if (ecs_poly_is(world, ecs_stage_t)) { + ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); - /* 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; + /* Index 0 is reserved for main stage */ + return stage->id; + } else if (ecs_poly_is(world, ecs_world_t)) { + return 0; } else { - content[size] = '\0'; + ecs_throw(ECS_INTERNAL_ERROR, NULL); } - - fclose(file); - - return content; error: - ecs_os_free(content); - return NULL; + return 0; } -int ecs_plecs_from_file( - ecs_world_t *world, - const char *filename) +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) { - 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; + 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; } -static -ecs_id_t flecs_script_tag( - ecs_entity_t script, - ecs_entity_t instance) +bool ecs_readonly_begin( + ecs_world_t *world) { - if (!instance) { - return ecs_pair_t(EcsScript, script); - } else { - return ecs_pair(EcsChildOf, instance); - } -} + ecs_poly_assert(world, ecs_world_t); -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)); -} + flecs_process_pending_tables(world); -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); + ecs_dbg_3("#[bold]readonly"); + ecs_log_push_3(); - 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); + 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); } - 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); - } + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); - 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); + /* 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 (is_defer) { - flecs_resume_readonly(real_world, &srs); + /* 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 result; + return is_readonly; } -ecs_entity_t ecs_script_init( - ecs_world_t *world, - const ecs_script_desc_t *desc) +void ecs_readonly_end( + ecs_world_t *world) { - 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; - } - } + ecs_poly_assert(world, ecs_world_t); + ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); - if (ecs_script_update(world, e, 0, script, NULL)) { - goto error; - } + /* After this it is safe again to mutate the world directly */ + ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); + ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); - if (script != desc->str) { - /* Safe cast, only happens when script is loaded from file */ - ecs_os_free((char*)script); - } + ecs_log_pop_3(); - return e; + flecs_stage_auto_merge(world); error: - if (script != desc->str) { - /* Safe cast, only happens when script is loaded from file */ - ecs_os_free((char*)script); - } - if (!desc->entity) { - ecs_delete(world, e); - } - return 0; + return; } -void FlecsScriptImport( +void ecs_merge( ecs_world_t *world) { - ECS_MODULE(world, FlecsScript); - ECS_IMPORT(world, FlecsMeta); - - 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_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 } - } - }); + 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; } -#endif - -/** - * @file addons/journal.c - * @brief Journal addon. - */ - - -#ifdef FLECS_JOURNAL - -static -char* flecs_journal_entitystr( +void ecs_set_automerge( ecs_world_t *world, - ecs_entity_t entity) + bool auto_merge) { - 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); + /* 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; } - } - 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); + /* 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 { - return ecs_id_str(world, id); + ecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + stage->auto_merge = auto_merge; } } -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) +bool ecs_stage_is_readonly( + const ecs_world_t *stage) { - flecs_journal_sp ++; - - if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { - return; - } + const ecs_world_t *world = ecs_get_world(stage); - char *path = NULL; - char *var_id = NULL; - if (entity) { - path = ecs_get_fullpath(world, entity); - var_id = flecs_journal_entitystr(world, entity); + if (ecs_poly_is(stage, ecs_stage_t)) { + if (((const ecs_stage_t*)stage)->async) { + return false; + } } - 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 (world->flags & EcsWorldReadonly) { + if (ecs_poly_is(stage, ecs_world_t)) { + return true; } - } - 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); + } else { + if (ecs_poly_is(stage, ecs_stage_t)) { + return true; } } - 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(); + return false; } -#endif - -/** - * @file addons/module.c - * @brief Module addon. - */ +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; -#ifdef FLECS_MODULE + flecs_defer_begin(world, stage); -#include + return (ecs_world_t*)stage; +} -char* ecs_module_path_from_c( - const char *c_name) +void ecs_async_stage_free( + ecs_world_t *world) { - 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_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; +} - ecs_strbuf_appendstrn(&str, &ch, 1); +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_strbuf_get(&str); + return ((ecs_stage_t*)stage)->async; } -ecs_entity_t ecs_import( - ecs_world_t *world, - ecs_module_action_t module, - const char *module_name) +bool ecs_is_deferred( + const ecs_world_t *world) { - 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; + 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; +} - 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(); +/** + * @file value.c + * @brief Utility functions to work with non-trivial pointers of user types. + */ - /* 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); +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_log_pop(); + ecs_xtor_t ctor; + if ((ctor = ti->hooks.ctor)) { + ctor(ptr, 1, ti); + } else { + ecs_os_memset(ptr, 0, ti->size); } - /* Restore to previous state */ - ecs_set_scope(world, old_scope); - world->info.name_prefix = old_name_prefix; - - return e; -error: return 0; +error: + return -1; } -ecs_entity_t ecs_import_c( - ecs_world_t *world, - ecs_module_action_t module, - const char *c_name) +int ecs_value_init( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) { - 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_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; } -ecs_entity_t ecs_import_from_library( +void* ecs_value_new_w_type_info( ecs_world_t *world, - const char *library_name, - const char *module_name) + const ecs_type_info_t *ti) { - ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); - - char *import_func = (char*)module_name; /* safe */ - char *module = (char*)module_name; + ecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - 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; + 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; } - /* 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 ++; - } - } - } + return result; +error: + return NULL; +} - *bptr = '\0'; +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"); - module = ecs_os_strdup(import_func); - ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); + return ecs_value_new_w_type_info(world, ti); +error: + return NULL; +} - ecs_os_strcat(bptr, "Import"); - } +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; - 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_xtor_t dtor; + if ((dtor = ti->hooks.dtor)) { + dtor(ptr, 1, ti); } - 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); + return 0; +error: + return -1; +} - if (module != module_name) { - ecs_os_free(module); - } +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; +} - return 0; - } else { - ecs_trace("library '%s' ('%s') loaded", - library_name, library_filename); +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; } - 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); - } + flecs_free(&world->allocator, ti->size, ptr); - /* Do not free id, as it will be stored as the component identifier */ - ecs_entity_t result = ecs_import(world, action, module); + return 0; +error: + return -1; +} - if (import_func != module_name) { - ecs_os_free(import_func); - } +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; - if (module != module_name) { - ecs_os_free(module); + ecs_copy_t copy; + if ((copy = ti->hooks.copy)) { + copy(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); } - ecs_os_free(library_filename); - - return result; -error: return 0; +error: + return -1; } -ecs_entity_t ecs_module_init( - ecs_world_t *world, - const char *c_name, - const ecs_component_desc_t *desc) +int ecs_value_copy( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + const void *src) { - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); 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; +} - ecs_entity_t old_scope = ecs_set_scope(world, 0); +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_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_move_t move; + if ((move = ti->hooks.move)) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); } - - ecs_add_id(world, e, EcsModule); - ecs_component_desc_t private_desc = *desc; - private_desc.entity = e; + return 0; +error: + return -1; +} - 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; - } +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; +} - ecs_set_scope(world, old_scope); +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 e; -error: return 0; +error: + return -1; } -#endif +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 addons/metrics.c - * @brief Metrics addon. + * @file world.c + * @brief World-level API. */ -#ifdef FLECS_METRICS +/* 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); -/* 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); +/** 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; -/* Internal components */ -ECS_COMPONENT_DECLARE(EcsMetricMember); -ECS_COMPONENT_DECLARE(EcsMetricId); -ECS_COMPONENT_DECLARE(EcsMetricOneOf); -ECS_COMPONENT_DECLARE(EcsMetricCountIds); -ECS_COMPONENT_DECLARE(EcsMetricCountTargets); -ECS_COMPONENT_DECLARE(EcsMetricMemberInstance); -ECS_COMPONENT_DECLARE(EcsMetricIdInstance); -ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance); +/* Poly target components */ +const ecs_entity_t EcsQuery = 5; +const ecs_entity_t EcsObserver = 6; +const ecs_entity_t EcsSystem = 7; -/** Context for metric */ -typedef struct { - ecs_entity_t metric; /**< Metric entity */ - ecs_entity_t kind; /**< Metric kind (gauge, counter) */ -} ecs_metric_ctx_t; +/* 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; -/** 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; +const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 8; +const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 9; -/** 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; +/* 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; -/** 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; +/* 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; -/** 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; +/* 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; -/** Stores context shared for all instances of member metric */ -typedef struct { - ecs_member_metric_ctx_t *ctx; -} EcsMetricMember; +/* 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; -/** Stores context shared for all instances of id metric */ -typedef struct { - ecs_id_metric_ctx_t *ctx; -} EcsMetricId; +/* 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; -/** Stores context shared for all instances of oneof metric */ -typedef struct { - ecs_oneof_metric_ctx_t *ctx; -} EcsMetricOneOf; +/* 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; -/** Stores context shared for all instances of id counter metric */ -typedef struct { - ecs_id_t id; -} EcsMetricCountIds; +/* 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; -/** Stores context shared for all instances of target counter metric */ -typedef struct { - ecs_count_targets_metric_ctx_t *ctx; -} EcsMetricCountTargets; +/* 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; -/** Instance of member metric */ -typedef struct { - ecs_ref_t ref; - ecs_member_metric_ctx_t *ctx; -} EcsMetricMemberInstance; +/* 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; -/** Instance of id metric */ -typedef struct { - ecs_record_t *r; - ecs_id_metric_ctx_t *ctx; -} EcsMetricIdInstance; +/* 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; -/** Instance of oneof metric */ -typedef struct { - ecs_record_t *r; - ecs_oneof_metric_ctx_t *ctx; -} EcsMetricOneOfInstance; +/** Meta module component ids */ +const ecs_entity_t ecs_id(EcsMetaType) = FLECS_HI_COMPONENT_ID + 97; +const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = FLECS_HI_COMPONENT_ID + 98; +const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 99; +const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 100; +const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 101; +const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 102; +const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 103; +const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 104; +const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 105; +const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 106; +const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 107; +const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 108; +const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 109; +const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 110; +const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 111; -/** Component lifecycle */ +/* Doc module components */ +const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 112; +const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 113; +const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 114; +const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 115; +const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 116; -static ECS_DTOR(EcsMetricMember, ptr, { - ecs_os_free(ptr->ctx); -}) +/* REST module components */ +const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 117; -static ECS_MOVE(EcsMetricMember, dst, src, { - *dst = *src; - src->ctx = NULL; -}) +/* Default lookup path */ +static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; -static ECS_DTOR(EcsMetricId, ptr, { - ecs_os_free(ptr->ctx); -}) +/* 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); -static ECS_MOVE(EcsMetricId, dst, src, { - *dst = *src; - src->ctx = NULL; -}) +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); -static ECS_DTOR(EcsMetricOneOf, ptr, { - if (ptr->ctx) { - ecs_map_fini(&ptr->ctx->target_offset); - ecs_os_free(ptr->ctx); - } -}) +ECS_DECLARE(EcsKibi); +ECS_DECLARE(EcsMebi); +ECS_DECLARE(EcsGibi); +ECS_DECLARE(EcsTebi); +ECS_DECLARE(EcsPebi); +ECS_DECLARE(EcsExbi); +ECS_DECLARE(EcsZebi); +ECS_DECLARE(EcsYobi); -static ECS_MOVE(EcsMetricOneOf, dst, src, { - *dst = *src; - src->ctx = NULL; -}) +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); -static ECS_DTOR(EcsMetricCountTargets, ptr, { - if (ptr->ctx) { - ecs_map_fini(&ptr->ctx->targets); - ecs_os_free(ptr->ctx); - } -}) +ECS_DECLARE(EcsTime); + ECS_DECLARE(EcsDate); -static ECS_MOVE(EcsMetricCountTargets, dst, src, { - *dst = *src; - src->ctx = NULL; -}) +ECS_DECLARE(EcsMass); + ECS_DECLARE(EcsGrams); + ECS_DECLARE(EcsKiloGrams); -/** 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); +ECS_DECLARE(EcsElectricCurrent); + ECS_DECLARE(EcsAmpere); - 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); +ECS_DECLARE(EcsAmount); + ECS_DECLARE(EcsMole); - 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); - } -} +ECS_DECLARE(EcsLuminousIntensity); + ECS_DECLARE(EcsCandela); -/** 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; +ECS_DECLARE(EcsForce); + ECS_DECLARE(EcsNewton); - 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); +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); - 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); - } -} +ECS_DECLARE(EcsPressure); + ECS_DECLARE(EcsPascal); + ECS_DECLARE(EcsBar); -/** 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_DECLARE(EcsSpeed); + ECS_DECLARE(EcsMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerHour); + ECS_DECLARE(EcsMilesPerHour); - ecs_world_t *world = it->world; - ecs_oneof_metric_ctx_t *ctx = it->ctx; +ECS_DECLARE(EcsAcceleration); - 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); +ECS_DECLARE(EcsTemperature); + ECS_DECLARE(EcsKelvin); + ECS_DECLARE(EcsCelsius); + ECS_DECLARE(EcsFahrenheit); - 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); - } -} +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); -/** 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); +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); - 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); - } - } -} +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 -/** 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); +/* -- Private functions -- */ - 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]); - } - } -} +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); -/** 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; + if (ecs_poly_is(world, ecs_world_t)) { + return &world->stages[0]; - 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]); - } + } else if (ecs_poly_is(world, ecs_stage_t)) { + return ECS_CONST_CAST(ecs_stage_t*, world); } + + return NULL; } -static void UpdateGaugeMemberInstance(ecs_iter_t *it) { - UpdateMemberInstance(it, false); -} +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr) +{ + ecs_world_t *world = *world_ptr; -static void UpdateCounterMemberInstance(ecs_iter_t *it) { - UpdateMemberInstance(it, false); -} + ecs_assert(ecs_poly_is(world, ecs_world_t) || + ecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); -static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { - UpdateMemberInstance(it, true); + 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); } -/** 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; +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); - 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_world_t *world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); + ecs_poly_assert(world, ecs_world_t); - 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]); - } + 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; } -} -static void UpdateGaugeIdInstance(ecs_iter_t *it) { - UpdateIdInstance(it, false); + 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->commands; + state->defer_stack = stage->defer_stack; + flecs_stack_init(&stage->defer_stack); + state->scope = stage->scope; + state->with = stage->with; + stage->defer = 0; + ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); + + return world; } -static void UpdateCounterIdInstance(ecs_iter_t *it) { - UpdateIdInstance(it, true); +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->commands, ecs_cmd_t); + stage->commands = state->commands; + flecs_stack_fini(&stage->defer_stack); + stage->defer_stack = state->defer_stack; + stage->scope = state->scope; + stage->with = state->with; + } } -/** Update oneof metric */ -static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { - ecs_world_t *world = it->real_world; - void *m = ecs_table_get_column(it->table, it->columns[0] - 1, it->offset); - EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 2); - ecs_ftime_t dt = it->delta_time; +/* 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); - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; - ecs_table_t *table = mi[i].r->table; + if (!world->monitors.is_dirty) { + return; + } - double *value = ECS_ELEM(m, ctx->size, i); - if (!counter) { - ecs_os_memset(value, 0, ctx->size); - } + world->monitors.is_dirty = false; - if (!table) { - ecs_delete(it->world, it->entities[i]); + 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; } - ecs_id_record_t *idr = ctx->idr; - ecs_id_t id; - if (flecs_search_w_idr(world, table, idr->id, &id, idr) == -1) { - ecs_delete(it->world, it->entities[i]); - continue; - } + m->is_dirty = false; - 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; + 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 + }); } + } +} - value = ECS_OFFSET(value, *offset); +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t id) +{ + ecs_map_t *monitors = &world->monitors.monitors; - if (!counter) { - *value = 1.0; - } else { - *value += 1.0 * (double)dt; + /* 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; } } } -static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { - UpdateOneOfInstance(it, false); -} +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); -static void UpdateCounterOneOfInstance(ecs_iter_t *it) { - UpdateOneOfInstance(it, true); + 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; } -static void UpdateCountTargets(ecs_iter_t *it) { - ecs_world_t *world = it->real_world; - EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 1); +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); - 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); - } + ecs_map_t *monitors = &world->monitors.monitors; + if (!ecs_map_is_init(monitors)) { + return; + } - EcsMetricSource *source = ecs_get_mut( - world, mi[0], EcsMetricSource); - source->entity = tgt; - } + ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); + if (!m) { + return; + } - EcsMetricValue *value = ecs_get_mut(world, mi[0], EcsMetricValue); - value->value += (double)ecs_count_id(world, cur->id) * - (double)it->delta_system_time; + 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; } } -} -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); + if (!count) { + ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); + ecs_map_remove_free(monitors, id); + } - 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; + if (!ecs_map_count(monitors)) { + ecs_map_fini(monitors); } } -/** Initialize member metric */ static -int flecs_member_metric_init( - ecs_world_t *world, - ecs_entity_t metric, - const ecs_metric_desc_t *desc) +void flecs_init_store( + ecs_world_t *world) { - 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; - } + 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); - const EcsPrimitive *p = ecs_get(world, m->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; - } + /* Initialize entity index */ + flecs_entities_init(world); - ecs_entity_t 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; - } + /* Initialize root table */ + flecs_sparse_init_t(&world->store.tables, + a, &world->allocators.sparse_chunk, ecs_table_t); - 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; - } + /* Initialize table map */ + flecs_table_hashmap_init(world, &world->store.table_map); - 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; - } + /* Initialize one root table per stage */ + flecs_init_root_table(world); +} - 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_ito(uint16_t, m->offset); +static +void flecs_clean_tables( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(&world->store.tables); - ecs_observer(world, { - .entity = metric, - .events = { EcsOnAdd }, - .filter.terms[0] = { - .id = type, - .src.flags = EcsSelf, - .inout = EcsInOutNone - }, - .callback = flecs_metrics_on_member_metric, - .yield_existing = true, - .ctx = ctx - }); + /* 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; - ecs_set_pair(world, metric, EcsMetricMember, desc->member, { .ctx = ctx }); - ecs_add_pair(world, metric, EcsMetric, desc->kind); - ecs_add_id(world, metric, EcsMetric); + 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); + } - return 0; -error: - return -1; + /* 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); + } } -/** Update id metric */ static -int flecs_id_metric_init( +void flecs_fini_root_tables( ecs_world_t *world, - ecs_entity_t metric, - const ecs_metric_desc_t *desc) + ecs_id_record_t *idr, + bool fini_targets) { - 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_table_cache_iter_t it; - 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 - }); + bool has_roots = flecs_table_cache_iter(&idr->cache, &it); + ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); + (void)has_roots; - ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); - ecs_add_pair(world, metric, EcsMetric, desc->kind); - ecs_add_id(world, metric, EcsMetric); + 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 */ + } - return 0; -error: - return -1; + 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]); + } + } + } + } } -/** 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) +void flecs_fini_roots( + ecs_world_t *world) { - 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); + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); - /* 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; - } + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); - char *to_snake_case = flecs_to_snake_case(name); + /* 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]); - ecs_entity_t mbr = ecs_entity(world, { - .name = to_snake_case, - .add = { ecs_childof(metric) } - }); + flecs_defer_begin(world, &world->stages[0]); + flecs_fini_root_tables(world, idr, false); + flecs_defer_end(world, &world->stages[0]); +} - ecs_os_free(to_snake_case); +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_set(world, mbr, EcsMember, { - .type = ecs_id(ecs_f64_t), - .unit = EcsSeconds - }); + 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); +} - /* 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; +/* 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; + } - offset += sizeof(double); - } + 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 + }; } +} - ctx->size = flecs_uto(ecs_size_t, offset); +static +void flecs_world_allocators_init( + ecs_world_t *world) +{ + ecs_world_allocators_t *a = &world->allocators; - 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 - }); + flecs_allocator_init(&world->allocator); - ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); - ecs_add_pair(world, metric, EcsMetric, desc->kind); - ecs_add_id(world, metric, EcsMetric); + ecs_map_params_init(&a->ptr, &world->allocator); + ecs_map_params_init(&a->query_table_list, &world->allocator); - return 0; -error: - return -1; + 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 -int flecs_count_id_targets_metric_init( - ecs_world_t *world, - ecs_entity_t metric, - const ecs_metric_desc_t *desc) +static +void flecs_world_allocators_fini( + ecs_world_t *world) { - 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_world_allocators_t *a = &world->allocators; - ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); - ecs_add_pair(world, metric, EcsMetric, desc->kind); - ecs_add_id(world, metric, EcsMetric); + ecs_map_params_fini(&a->ptr); + ecs_map_params_fini(&a->query_table_list); - return 0; -error: - return -1; + 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 -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; +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(); } -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); +/* -- Public functions -- */ - ecs_entity_t result = desc->entity; - if (!result) { - result = ecs_new_id(world); - } +ecs_world_t *ecs_mini(void) { +#ifdef FLECS_OS_API_IMPL + ecs_set_os_api_impl(); +#endif + ecs_os_init(); - ecs_entity_t kind = desc->kind; - if (!kind) { - ecs_err("missing metric kind"); - goto error; - } + ecs_trace("#[bold]bootstrapping world"); + ecs_log_push(); - if (kind != EcsGauge && - kind != EcsCounter && - kind != EcsCounterId && - kind != EcsCounterIncrement) - { - ecs_err("invalid metric kind %s", ecs_get_fullpath(world, kind)); - goto error; - } + ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); - if (kind == EcsCounterIncrement && !desc->member) { - ecs_err("CounterIncrement can only be used in combination with member"); - goto error; + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); } - if (kind == EcsCounterId && desc->member) { - ecs_err("CounterIncrement cannot be used in combination with member"); - goto error; + if (!ecs_os_has_threading()) { + ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } - 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 (!ecs_os_has_time()) { + ecs_trace("time management not available"); } - if (desc->member) { - if (desc->id) { - ecs_err("cannot specify both member and id for metric"); - goto error; - } - 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; - } + flecs_log_addons(); - 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; - } +#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 - 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; - } +#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 - return result; -error: - if (result && result != desc->entity) { - ecs_delete(world, result); - } - return 0; -} + 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); -void FlecsMetricsImport(ecs_world_t *world) { - ECS_MODULE_DEFINE(world, FlecsMetrics); + world->flags |= EcsWorldInit; - ECS_IMPORT(world, FlecsPipeline); - ECS_IMPORT(world, FlecsMeta); - ECS_IMPORT(world, FlecsUnits); + flecs_world_allocators_init(world); + ecs_allocator_t *a = &world->allocator; - 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); + 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; - 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); + 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*); - ecs_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); - ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); - ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); + 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); - ecs_struct(world, { - .entity = ecs_id(EcsMetricValue), - .members = { - { .name = "value", .type = ecs_id(ecs_f64_t) } - } - }); + world->info.time_scale = 1.0; + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); + } - ecs_struct(world, { - .entity = ecs_id(EcsMetricSource), - .members = { - { .name = "entity", .type = ecs_id(ecs_entity_t) } - } - }); + 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); - ecs_set_hooks(world, EcsMetricMember, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsMetricMember), - .move = ecs_move(EcsMetricMember) - }); + flecs_bootstrap(world); - ecs_set_hooks(world, EcsMetricId, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsMetricId), - .move = ecs_move(EcsMetricId) - }); + world->flags &= ~EcsWorldInit; - ecs_set_hooks(world, EcsMetricOneOf, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsMetricOneOf), - .move = ecs_move(EcsMetricOneOf) - }); + ecs_trace("world ready!"); + ecs_log_pop(); - ecs_set_hooks(world, EcsMetricCountTargets, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsMetricCountTargets), - .move = ecs_move(EcsMetricCountTargets) - }); + return world; +} - ecs_add_id(world, EcsMetric, EcsOneOf); +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); -#ifdef FLECS_DOC - ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, EcsMetricSource); +#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 - - 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); -} - +#ifdef FLECS_PIPELINE + ECS_IMPORT(world, FlecsPipeline); +#endif +#ifdef FLECS_TIMER + ECS_IMPORT(world, FlecsTimer); #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); - + ECS_IMPORT(world, FlecsMeta); #endif - +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); #endif - - -#ifdef FLECS_META - -ecs_entity_t ecs_primitive_init( - ecs_world_t *world, - const ecs_primitive_desc_t *desc) -{ - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_set(world, t, EcsPrimitive, { desc->kind }); - - return t; +#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_entity_t ecs_enum_init( - ecs_world_t *world, - const ecs_enum_desc_t *desc) +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_add(world, t, EcsEnum); + ecs_world_t *world = ecs_init(); - ecs_entity_t old_scope = ecs_set_scope(world, t); + (void)argc; + (void)argv; - 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; +#ifdef FLECS_DOC + if (argc) { + char *app = argv[0]; + char *last_elem = strrchr(app, '/'); + if (!last_elem) { + last_elem = strrchr(app, '\\'); } - - 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}); + if (last_elem) { + app = last_elem + 1; } + ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } +#endif - ecs_set_scope(world, old_scope); + return world; +} - if (i == 0) { - ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } +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; +} - return t; +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; } -ecs_entity_t ecs_bitmask_init( +void flecs_notify_tables( ecs_world_t *world, - const ecs_bitmask_desc_t *desc) + ecs_id_t id, + ecs_table_event_t *event) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_add(world, t, EcsBitmask); + ecs_poly_assert(world, ecs_world_t); - ecs_entity_t old_scope = ecs_set_scope(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); + } - 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; + /* 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_entity_t c = ecs_entity(world, { - .name = m_desc->name - }); + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; - if (!m_desc->value) { - ecs_add_id(world, c, EcsConstant); - } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, - {m_desc->value}); + 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); } } +} - ecs_set_scope(world, old_scope); - - if (i == 0) { - ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } +void ecs_default_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ti) +{ + ecs_os_memset(ptr, 0, ti->size * count); +} - return t; +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); } -ecs_entity_t ecs_array_init( - ecs_world_t *world, - const ecs_array_desc_t *desc) +static +void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->move(dst_ptr, src_ptr, count, ti); +} - ecs_set(world, t, EcsArray, { - .type = desc->type, - .count = desc->count - }); +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); +} - return t; +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); } -ecs_entity_t ecs_vector_init( - ecs_world_t *world, - const ecs_vector_desc_t *desc) +static +void flecs_default_move(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move(dst_ptr, src_ptr, count, ti); +} - ecs_set(world, t, EcsVector, { - .type = desc->type - }); +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); +} - return t; +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); } -ecs_entity_t ecs_struct_init( +void ecs_set_hooks_id( ecs_world_t *world, - const ecs_struct_desc_t *desc) + ecs_entity_t component, + const ecs_type_hooks_t *h) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t old_scope = ecs_set_scope(world, t); + flecs_stage_from_world(&world); - 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; - } + /* 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)); - if (!m_desc->name) { - ecs_err("member %d of struct '%s' does not have a name", i, - ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } + ecs_type_info_t *ti = flecs_type_info_ensure(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t m = ecs_entity(world, { - .name = m_desc->name - }); + ecs_check(!ti->component || ti->component == component, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - ecs_set(world, m, EcsMember, { - .type = m_desc->type, - .count = m_desc->count, - .offset = m_desc->offset, - .unit = m_desc->unit - }); - } + if (!ti->size) { + const EcsComponent *component_ptr = ecs_get( + world, component, EcsComponent); - ecs_set_scope(world, old_scope); + /* 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); - if (i == 0) { - ecs_err("struct '%s' has no members", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } + ti->size = component_ptr->size; + ti->alignment = component_ptr->alignment; + } - if (!ecs_has(world, t, EcsStruct)) { - /* Invalid members */ - ecs_delete(world, t); - return 0; + 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; } - return t; -} + /* Set default copy ctor, move ctor and merge */ + if (h->copy && !h->copy_ctor) { + ti->hooks.copy_ctor = flecs_default_copy_ctor; + } -ecs_entity_t ecs_opaque_init( - ecs_world_t *world, - const ecs_opaque_desc_t *desc) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); + if (h->move && !h->move_ctor) { + ti->hooks.move_ctor = flecs_default_move_ctor; + } - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + 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; + } + } + } } - ecs_set_ptr(world, t, EcsOpaque, &desc->type); + 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; + } + } + } - return t; +error: + return; } -ecs_entity_t ecs_unit_init( +const ecs_type_hooks_t* ecs_get_hooks_id( ecs_world_t *world, - const ecs_unit_desc_t *desc) + ecs_entity_t id) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + return &ti->hooks; } + return NULL; +} - 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); - } +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); - 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); + 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); - if (!flecs_unit_validate(world, t, value)) { - goto error; - } + elem->action = action; + elem->ctx = ctx; +error: + return; +} - ecs_modified(world, t, EcsUnit); +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); - return t; + elem->action = action; + elem->ctx = ctx; error: - if (t) { - ecs_delete(world, t); + 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); } - return 0; } -ecs_entity_t ecs_unit_prefix_init( - ecs_world_t *world, - const ecs_unit_prefix_desc_t *desc) +/* Invoke fini actions */ +static +void flecs_fini_actions( + ecs_world_t *world) { - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(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_set(world, t, EcsUnitPrefix, { - .symbol = (char*)desc->symbol, - .translation = desc->translation - }); + ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); +} - return 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 ecs_quantity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc) +ecs_entity_t flecs_get_oneof( + const ecs_world_t *world, + ecs_entity_t e) { - ecs_entity_t t = ecs_entity_init(world, desc); - if (!t) { + 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; } +} - ecs_add_id(world, t, EcsQuantity); +/* 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"); - return t; -} + ecs_trace("#[bold]shutting down world"); + ecs_log_push(); -#endif + world->flags |= EcsWorldQuit; -/** - * @file meta/serialized.c - * @brief Serialize type into flat operations array to speed up deserialization. - */ + /* 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; -#ifdef FLECS_META + /* 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(); -static -int flecs_meta_serialize_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops); + ecs_dbg_1("#[bold]cleanup remaining entities"); + ecs_log_push_1(); -ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) { - return EcsOpPrimitive + kind; -} + /* Operations invoked during UnSet/OnRemove/destructors are deferred and + * will be discarded after world cleanup */ + flecs_defer_begin(world, &world->stages[0]); -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; -} + /* Run UnSet/OnRemove actions for components while the store is still + * unmodified by cleanup. */ + flecs_fini_unset_tables(world); -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->unit = 0; - return op; + /* 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 datastructures"); + 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; } -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; +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); } -static -int flecs_meta_serialize_primitive( +void ecs_dim( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + int32_t entity_count) { - 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_poly_assert(world, ecs_world_t); + flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); +} - 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; +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); } -static -int flecs_meta_serialize_enum( +void ecs_measure_frame_time( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + bool enable) { - (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; + 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; } -static -int flecs_meta_serialize_bitmask( +void ecs_measure_system_time( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + bool enable) { - (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; + 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; } -static -int flecs_meta_serialize_array( +void ecs_set_target_fps( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + ecs_ftime_t fps) { - (void)world; + ecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - 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; + ecs_measure_frame_time(world, true); + world->info.target_fps = fps; +error: + return; } -static -int flecs_meta_serialize_array_component( - ecs_world_t *world, - ecs_entity_t type, - ecs_vec_t *ops) +void* ecs_get_ctx( + const ecs_world_t *world) { - const EcsArray *ptr = ecs_get(world, type, EcsArray); - if (!ptr) { - return -1; /* Should never happen, will trigger internal error */ - } - - flecs_meta_serialize_type(world, ptr->type, 0, ops); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->ctx; +error: + return NULL; +} - ecs_meta_type_op_t *first = ecs_vec_first(ops); - first->count = ptr->count; - return 0; +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; } -static -int flecs_meta_serialize_vector( +void ecs_set_ctx( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + void *ctx, + ecs_ctx_free_t ctx_free) { - (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; + ecs_poly_assert(world, ecs_world_t); + world->ctx = ctx; + world->ctx_free = ctx_free; } -static -int flecs_meta_serialize_custom_type( +void ecs_set_binding_ctx( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + void *ctx, + ecs_ctx_free_t ctx_free) { - (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; + ecs_poly_assert(world, ecs_world_t); + world->binding_ctx = ctx; + world->binding_ctx_free = ctx_free; } -static -int flecs_meta_serialize_struct( +void ecs_set_entity_range( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + ecs_entity_t id_start, + ecs_entity_t id_end) { - 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_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); - ecs_member_t *members = ecs_vec_first(&ptr->members); - int32_t i, count = ecs_vec_count(&ptr->members); + uint32_t start = (uint32_t)id_start; + uint32_t end = (uint32_t)id_end; - ecs_hashmap_t *member_index = NULL; - if (count) { - op->members = member_index = flecs_name_index_new( - world, &world->allocator); + if (flecs_entities_max_id(world) < start) { + flecs_entities_max_id(world) = start - 1; } - for (i = 0; i < count; i ++) { - ecs_member_t *member = &members[i]; - - cur = ecs_vec_count(ops); - flecs_meta_serialize_type(world, member->type, offset + member->offset, ops); - - 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->unit = member->unit; - op->op_count = ecs_vec_count(ops) - cur; + world->info.min_id = start; + world->info.max_id = end; +error: + return; +} - flecs_name_index_ensure( - member_index, flecs_ito(uint64_t, cur - first - 1), - member_name, 0, 0); - } +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; +} - flecs_meta_ops_add(ops, EcsOpPop); - flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first; +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; } -static -int flecs_meta_serialize_type( +void ecs_set_entity_generation( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) + ecs_entity_t entity_with_generation) { - 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; - } + 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); - 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); - } + flecs_entities_set_generation(world, entity_with_generation); - return 0; + 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; + } } -static -int flecs_meta_serialize_component( - ecs_world_t *world, - ecs_entity_t type, - ecs_vec_t *ops) +const ecs_type_info_t* flecs_type_info_get( + const ecs_world_t *world, + ecs_entity_t component) { - 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; - } + ecs_poly_assert(world, ecs_world_t); - switch(ptr->kind) { - case EcsArrayType: - return flecs_meta_serialize_array_component(world, type, ops); - break; - default: - return flecs_meta_serialize_type(world, type, 0, ops); - break; - } + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); - return 0; + return flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); } -void ecs_meta_type_serialized_init( - ecs_iter_t *it) +ecs_type_info_t* flecs_type_info_ensure( + ecs_world_t *world, + ecs_entity_t component) { - ecs_world_t *world = it->world; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - 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); + 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); + } - EcsMetaTypeSerialized *ptr = ecs_get_mut( - world, e, EcsMetaTypeSerialized); - if (ptr->ops.array) { - ecs_meta_dtor_serialized(ptr); + 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); + } } - - ptr->ops = ops; } -} -#endif - -/** - * @file meta/meta.c - * @brief Meta addon. - */ + 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; -#ifdef FLECS_META + flecs_entities_ensure(world, component); -/* ecs_string_t lifecycle */ + 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); + } + } -static ECS_COPY(ecs_string_t, dst, src, { - ecs_os_free(*(ecs_string_t*)dst); - *(ecs_string_t*)dst = ecs_os_strdup(*(ecs_string_t*)src); -}) + /* 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; -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; -}) + /* 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)); -static ECS_DTOR(ecs_string_t, ptr, { - ecs_os_free(*(ecs_string_t*)ptr); - *(ecs_string_t*)ptr = NULL; -}) + /* 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); -/* EcsMetaTypeSerialized lifecycle */ + return changed; +} -void ecs_meta_dtor_serialized( - EcsMetaTypeSerialized *ptr) +void flecs_type_info_fini( + ecs_type_info_t *ti) { - 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); - } + 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; } - - 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); +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; + } - 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); - } + 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_MOVE(EcsMetaTypeSerialized, dst, src, { - ecs_meta_dtor_serialized(dst); - dst->ops = src->ops; - src->ops = (ecs_vec_t){0}; -}) +static +ecs_ftime_t flecs_insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) +{ + ecs_poly_assert(world, ecs_world_t); -static ECS_DTOR(EcsMetaTypeSerialized, ptr, { - ecs_meta_dtor_serialized(ptr); -}) + 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; + } -/* EcsStruct lifecycle */ + ecs_ftime_t target_delta_time = + ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); -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((char*)members[i].name); - } - ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); -} + /* 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; -static ECS_COPY(EcsStruct, dst, src, { - flecs_struct_dtor(dst); + /* 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; - dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); + 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); + } - ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); - int32_t m, count = ecs_vec_count(&dst->members); + now = start; + delta_time = (ecs_ftime_t)ecs_time_measure(&now); + } while ((target_delta_time - delta_time) > + (sleep_time / (ecs_ftime_t)2.0)); - for (m = 0; m < count; m ++) { - members[m].name = ecs_os_strdup(members[m].name); - } -}) + *stop = now; + return delta_time; +} -static ECS_MOVE(EcsStruct, dst, src, { - flecs_struct_dtor(dst); - dst->members = src->members; - src->members = (ecs_vec_t){0}; -}) +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); -static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) + 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)); -/* EcsEnum lifecycle */ + world->frame_start_time = t; -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((char*)c->name); - ecs_os_free(c); + /* Keep track of total time passed in world */ + world->info.world_time_total_raw += (ecs_ftime_t)delta_time; } - ecs_map_fini(constants); + + return (ecs_ftime_t)delta_time; } -static void flecs_constants_copy( - ecs_map_t *dst, - ecs_map_t *src) +static +void flecs_stop_measure_frame( + ecs_world_t* world) { - ecs_map_copy(dst, src); + ecs_poly_assert(world, ecs_world_t); - 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; + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_t t = world->frame_start_time; + world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } -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); -}) +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"); -static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) + /* 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; -/* EcsBitmask lifecycle */ + /* Keep track of total scaled time passed in world */ + world->info.world_time_total += world->info.delta_time; -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); -}) + ecs_run_aperiodic(world, 0); -static ECS_MOVE(EcsBitmask, dst, src, { - flecs_constants_dtor(&dst->constants); - dst->constants = src->constants; - ecs_os_zeromem(&src->constants); -}) + return world->info.delta_time; +error: + return (ecs_ftime_t)0; +} -static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) +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]); + } -/* EcsUnit lifecycle */ + flecs_stop_measure_frame(world); +error: + return; +} -static void dtor_unit( - EcsUnit *ptr) +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world) { - ecs_os_free(ptr->symbol); + world = ecs_get_world(world); + return &world->info; } -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; +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_poly_assert(world, ecs_world_t); + flecs_table_free(world, table); +} - src->symbol = NULL; - src->base = 0; - src->over = 0; - src->prefix = 0; - src->translation = (ecs_unit_translation_t){0}; -}) +static +void flecs_process_empty_queries( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); -static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(ecs_id(EcsPoly), EcsQuery)); + if (!idr) { + return; + } + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); -/* EcsUnitPrefix lifecycle */ + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + flecs_defer_begin(world, &world->stages[0]); -static void dtor_unit_prefix( - EcsUnitPrefix *ptr) -{ - ecs_os_free(ptr->symbol); -} + 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); -static ECS_COPY(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = ecs_os_strdup(src->symbol); - dst->translation = src->translation; -}) + 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); + } + } + } + } -static ECS_MOVE(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = src->symbol; - dst->translation = src->translation; + flecs_defer_end(world, &world->stages[0]); +} - src->symbol = NULL; - src->translation = (ecs_unit_translation_t){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); -static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) + /* 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; + } -/* Type initialization */ + /* 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; + } -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"; + /* 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 datastructures is + * more predictable. */ + int32_t i, count = flecs_sparse_count(world->pending_tables); + if (!count) { + return; } -} -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); + flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); - EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType); - if (meta_type->kind == 0) { - meta_type->existing = ecs_has(world, type, EcsComponent); + do { + ecs_sparse_t *pending_tables = world->pending_tables; + world->pending_tables = world->pending_buffer; + world->pending_buffer = NULL; - /* 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; - } - } + /* 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]); - 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) { - 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); - return -1; + 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; + } } - - meta_type->partial = comp->size != size; - } - meta_type->kind = kind; - meta_type->size = size; - meta_type->alignment = alignment; - ecs_modified(world, type, EcsMetaType); + flecs_sparse_clear(pending_tables); + ecs_defer_end(world); - return 0; -} + world->pending_buffer = pending_tables; + } while ((count = flecs_sparse_count(world->pending_tables))); -#define init_type_t(world, type, kind, T) \ - flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + flecs_journal_end(); +} -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) +void flecs_table_set_empty( + ecs_world_t *world, + ecs_table_t *table) { - member->member = entity; - member->type = type; - member->count = count; - member->unit = unit; - member->offset = offset; + 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 (!count) { - member->count = 1; + if (ecs_table_count(table)) { + table->_->generation = 0; } - ecs_os_strset((char**)&member->name, name); + flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, + (uint32_t)table->id)[0] = table; } -static -int flecs_add_member_to_struct( +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_entity_t type, - ecs_entity_t member, - EcsMember *m) + ecs_flags32_t flags) { - 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); + 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); + } +} - 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; +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 (!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_NEQZERO(time_budget_seconds)) { + time_budget = true; } - 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; + if (!id) { + id = EcsAny; /* Iterate all empty tables */ } - ecs_entity_t unit = m->unit; + 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; + } + } - 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; - } + ecs_table_t *table = tr->hdr.table; + ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); - 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; + 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 ++; + } + } } } - 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); - break; +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)); } } - /* 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); + return delete_count; +} - /* Reobtain members array in case it was reallocated */ - members = ecs_vec_first_t(&s->members, ecs_member_t); - count ++; - } +/** + * @file addons/alerts.c + * @brief Alerts addon. + */ - 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; +#ifdef FLECS_ALERTS - if (!explicit_offset) { - for (i = 0; i < count; i ++) { - ecs_member_t *elem = &members[i]; +ECS_COMPONENT_DECLARE(FlecsAlerts); - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); +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; - /* 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, member); - ecs_err("member '%s' is not a type", path); - ecs_os_free(path); - return -1; - } +typedef struct EcsAlertTimeout { + ecs_ftime_t inactive_time; /* Time the alert has been inactive */ + ecs_ftime_t expire_time; /* Expiration duration */ +} EcsAlertTimeout; - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; +ECS_COMPONENT_DECLARE(EcsAlertTimeout); - if (!member_size || !member_alignment) { - char *path = ecs_get_fullpath(world, member); - ecs_err("member '%s' has 0 size/alignment"); - ecs_os_free(path); - return -1; - } +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); +}) - member_size *= elem->count; - size = ECS_ALIGN(size, member_alignment); - elem->size = member_size; - elem->offset = size; +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); +}) - /* 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; - } +static +ECS_MOVE(EcsAlert, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; - size += member_size; + ecs_map_fini(&dst->instances); + dst->instances = src->instances; + src->instances = (ecs_map_t){0}; - if (member_alignment > alignment) { - alignment = member_alignment; - } - } - } else { - /* If members have explicit offsets, we can't rely on computed - * size/alignment values. Grab size of just added member instead. It - * doesn't matter if the size doesn't correspond to the actual struct - * size. The flecs_init_type function compares computed size with actual - * (component) size to determine if the type is partial. */ - const EcsComponent *cptr = ecs_get(world, m->type, EcsComponent); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - size = cptr->size; - alignment = cptr->alignment; - } + 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; +}) - if (size == 0) { - ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); - return -1; - } +static +ECS_CTOR(EcsAlertsActive, ptr, { + ecs_map_init(&ptr->alerts, NULL); + ptr->info_count = 0; + ptr->warning_count = 0; + ptr->error_count = 0; +}) - if (alignment == 0) { - ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); - return -1; - } +static +ECS_DTOR(EcsAlertsActive, ptr, { + ecs_map_fini(&ptr->alerts); +}) - /* Align struct size to struct alignment */ - size = ECS_ALIGN(size, alignment); +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}; +}) - ecs_modified(world, type, EcsStruct); +static +ECS_DTOR(EcsAlertInstance, ptr, { + ecs_os_free(ptr->message); +}) - /* Do this last as it triggers the update of EcsMetaTypeSerialized */ - if (flecs_init_type(world, type, EcsStructType, size, alignment)) { - return -1; - } +static +ECS_MOVE(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; +}) - /* 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); +static +ECS_COPY(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = ecs_os_strdup(src->message); +}) - type_mbr->type = type; - type_mbr->count = 1; +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_modified(world, type, EcsMember); + 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 ++; } - return 0; + 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 -int flecs_add_constant_to_enum( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) +void flecs_alerts_remove_alert_from_src( + ecs_world_t *world, + ecs_entity_t source, + ecs_entity_t alert) { - EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum); + EcsAlertsActive *active = ecs_get_mut( + world, source, EcsAlertsActive); + ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_remove(&active->alerts, alert); - /* 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((char*)c->name); - ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); - } + 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 --; } - /* 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; + if (!ecs_map_count(&active->alerts)) { + ecs_remove(world, source, EcsAlertsActive); + } else { + ecs_modified(world, source, EcsAlertsActive); } +} - /* 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; +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 { - if (c->value >= value) { - value = c->value + 1; + 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; + } } } } - 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) +ecs_entity_t flecs_alert_out_of_range_kind( + EcsAlert *alert, + const EcsMemberRanges *ranges, + const void *value_ptr) { - 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((char*)c->name); - ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); - } + 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: + return 0; } - /* 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; - } + bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); + bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); - 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; + 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 { - value = 1u << (ecs_u32_t)ecs_map_count(&ptr->constants); + return 0; } +} - /* 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; - } - } +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); - ecs_map_init_if(&ptr->constants, &world->allocator); + 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_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_id_t member_id = alert[i].id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges); + } - 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; + ecs_iter_t rit = ecs_rule_iter(world, rule); + rit.flags |= EcsIterNoData; + rit.flags |= EcsIterIsInstanced; - cptr = ecs_get_mut_id(world, e, type); - cptr[0] = value; + 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); + } - return 0; -} + 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; + } + } -static -void flecs_set_primitive(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); + 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 + }); + } - 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; + 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 flecs_set_member(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsMember *member = ecs_field(it, EcsMember, 1); +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); - 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; - } + /* 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); - flecs_add_member_to_struct(world, parent, e, &member[i]); + ecs_id_t member_id = alert->id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); } -} -static -void flecs_add_enum(ecs_iter_t *it) { - ecs_world_t *world = it->world; + ecs_vars_t vars = {0}; + ecs_vars_init(world, &vars); - int i, count = it->count; + int32_t i, count = it->count; for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; + ecs_entity_t ai = it->entities[i]; + ecs_entity_t e = source[i].entity; - if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { + /* 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; } - ecs_add_id(world, e, EcsExclusive); - ecs_add_id(world, e, EcsOneOf); - ecs_add_id(world, e, EcsTag); - } -} + /* 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); -static -void flecs_add_bitmask(ecs_iter_t *it) { - ecs_world_t *world = it->world; + if (ecs_rule_next(&rit)) { + bool match = true; - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; + /* 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); + } - if (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { - continue; - } - } -} + 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; + } + } + } -static -void flecs_add_constant(ecs_iter_t *it) { - ecs_world_t *world = it->world; + if (match) { + /* Only increase alert duration if the alert was active */ + value[i].value += (double)it->delta_system_time; - 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; - } + 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 (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); - } - } -} + if (generate_message) { + if (alert_instance[i].message) { + ecs_os_free(alert_instance[i].message); + } -static -void flecs_set_array(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsArray *array = ecs_field(it, EcsArray, 1); + ecs_iter_to_vars(&rit, &vars, 0); + alert_instance[i].message = ecs_interpolate_string( + world, alert->message, &vars); + } - 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 (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; + } - if (!elem_type) { - ecs_err("array '%s' has no element type", ecs_get_name(world, e)); - continue; - } + /* Alert instance still matches rule, keep it alive */ + ecs_iter_fini(&rit); + continue; + } - if (!elem_count) { - ecs_err("array '%s' has size 0", ecs_get_name(world, e)); - continue; + ecs_iter_fini(&rit); } - 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; + /* 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); } -static -void flecs_set_vector(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsVector *array = ecs_field(it, EcsVector, 1); +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); - 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; + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world, 0); + } - if (!elem_type) { - ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); - continue; - } + ecs_filter_desc_t private_desc = desc->filter; + private_desc.entity = result; - if (init_type_t(world, e, EcsVectorType, ecs_vec_t)) { - continue; - } + ecs_rule_t *rule = ecs_rule_init(world, &private_desc); + if (!rule) { + ecs_err("failed to create alert filter"); + return 0; } -} -static -void flecs_set_custom_type(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsOpaque *serialize = ecs_field(it, EcsOpaque, 1); + 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; + } - 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; + /* 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; - if (!elem_type) { - ecs_err("custom type '%s' has no mapping type", ecs_get_name(world, e)); - continue; + /* 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; + } + } } + } - 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; + /* 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; } - if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { - continue; + 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; } - } -} - -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)); + 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; } - } - if (over) { - if (!base) { - ecs_err("invalid unit '%s': cannot specify over without base", - ecs_get_name(world, t)); + 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 (!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)); + if (!member->type) { + ecs_err("ecs_alert_desc_t::member must have a type"); goto error; } - } - - if (prefix) { - if (!base) { - ecs_err("invalid unit '%s': cannot specify prefix without base", - ecs_get_name(world, t)); + + 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; } - 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)); + + if (!ecs_has(world, desc->member, EcsMemberRanges)) { + ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); 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)); + 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; } - } else { - translation = prefix_ptr->translation; } + + 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; } - 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; - } - } + ecs_modified(world, result, EcsAlert); - 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); - } + /* Register alert as metric */ + ecs_add(world, result, EcsMetric); + ecs_add_pair(world, result, EcsMetric, EcsCounter); - 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; - } - } + /* Add severity to alert */ + ecs_entity_t severity = desc->severity; + if (!severity) { + severity = EcsAlertError; + } - derived_symbol = ecs_strbuf_get(&sbuf); - if (derived_symbol && !ecs_os_strlen(derived_symbol)) { - ecs_os_free(derived_symbol); - derived_symbol = NULL; - } + ecs_add_pair(world, result, ecs_id(EcsAlert), severity); - 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); - } + 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 } - data->base = base; - data->over = over; - data->prefix = prefix; - data->translation = translation; + 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 true; + return result; error: - ecs_os_free(derived_symbol); - return false; + if (result) { + ecs_delete(world, result); + } + return 0; } -static -void flecs_set_unit(ecs_iter_t *it) { - EcsUnit *u = ecs_field(it, EcsUnit, 1); - - ecs_world_t *world = it->world; +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); - 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]); + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { + return 0; } -} - -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); - } + if (alert) { + return ecs_map_get(&active->alerts, alert) != NULL; } + + return ecs_map_count(&active->alerts); +error: + return 0; } -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); +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); - 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 - }); - } - } + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { + return 0; } -} -static -void flecs_member_on_set(ecs_iter_t *it) { - EcsMember *mbr = it->ptrs[0]; - if (!mbr->count) { - mbr->count = 1; + ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); + if (ptr) { + return ptr[0]; } + +error: + return 0; } -void FlecsMetaImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsMeta); +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); - 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, 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); + ecs_set_name_prefix(world, "EcsAlert"); + ECS_COMPONENT_DEFINE(world, EcsAlertInstance); + ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); - flecs_bootstrap_tag(world, EcsConstant); - flecs_bootstrap_tag(world, EcsQuantity); + ECS_TAG_DEFINE(world, EcsAlertInfo); + ECS_TAG_DEFINE(world, EcsAlertWarning); + ECS_TAG_DEFINE(world, EcsAlertError); + ECS_TAG_DEFINE(world, EcsAlertCritical); - ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); + 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_set_hooks(world, EcsMetaTypeSerialized, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsMetaTypeSerialized), - .copy = ecs_copy(EcsMetaTypeSerialized), - .dtor = ecs_dtor(EcsMetaTypeSerialized) + ecs_struct(world, { + .entity = ecs_id(EcsAlertInstance), + .members = { + { .name = "message", .type = ecs_id(ecs_string_t) } + } }); - 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, EcsAlert, { + .ctor = ecs_ctor(EcsAlert), + .dtor = ecs_dtor(EcsAlert), + .move = ecs_move(EcsAlert) }); - ecs_set_hooks(world, EcsMember, { - .ctor = ecs_default_ctor, - .on_set = flecs_member_on_set + ecs_set_hooks(world, EcsAlertsActive, { + .ctor = ecs_ctor(EcsAlertsActive), + .dtor = ecs_dtor(EcsAlertsActive), + .move = ecs_move(EcsAlertsActive) }); - ecs_set_hooks(world, EcsEnum, { + ecs_set_hooks(world, EcsAlertInstance, { .ctor = ecs_default_ctor, - .move = ecs_move(EcsEnum), - .copy = ecs_copy(EcsEnum), - .dtor = ecs_dtor(EcsEnum) + .dtor = ecs_dtor(EcsAlertInstance), + .move = ecs_move(EcsAlertInstance), + .copy = ecs_copy(EcsAlertInstance) }); - ecs_set_hooks(world, EcsBitmask, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsBitmask), - .copy = ecs_copy(EcsBitmask), - .dtor = ecs_dtor(EcsBitmask) + 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_set_hooks(world, EcsUnit, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsUnit), - .copy = ecs_copy(EcsUnit), - .dtor = ecs_dtor(EcsUnit) - }); + ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); + ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, + flecs.metrics.Source, flecs.metrics.Value, ?EcsAlertTimeout, ?Disabled); - ecs_set_hooks(world, EcsUnitPrefix, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsUnitPrefix), - .copy = ecs_copy(EcsUnitPrefix), - .dtor = ecs_dtor(EcsUnitPrefix) + ecs_system(world, { + .entity = ecs_id(MonitorAlerts), + .no_readonly = true, + .interval = 0.5 }); - /* 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_system(world, { + .entity = ecs_id(MonitorAlertInstances), + .interval = 0.5 }); +} - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = flecs_set_member - }); +#endif - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, - .events = {EcsOnAdd}, - .callback = flecs_add_enum - }); +/** + * @file addons/app.c + * @brief App addon. + */ - 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 - }); +#ifdef FLECS_APP - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = flecs_add_constant - }); +static +int flecs_default_run_action( + ecs_world_t *world, + ecs_app_desc_t *desc) +{ + if (desc->init) { + desc->init(world); + } - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = flecs_set_array - }); + 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) { } + } - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = flecs_set_vector - }); + /* Ensure quit flag is set on world, which can be used to determine if + * world needs to be cleaned up. */ + ecs_quit(world); - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = flecs_set_custom_type - }); + if (result == 1) { + return 0; /* Normal exit */ + } else { + return result; /* Error code */ + } +} - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = flecs_set_unit - }); +static +int flecs_default_frame_action( + ecs_world_t *world, + const ecs_app_desc_t *desc) +{ + return !ecs_progress(world, desc->delta_time); +} - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = ecs_meta_type_serialized_init - }); +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; - ecs_observer(world, { - .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, - .events = {EcsOnSet}, - .callback = ecs_meta_type_init_default_ctor - }); +/* Serve REST API from wasm image when running in emscripten */ +#ifdef ECS_TARGET_EM +#include - ecs_observer(world, { - .filter.terms = { - { .id = ecs_id(EcsUnit) }, - { .id = EcsQuantity } - }, - .events = { EcsMonitor }, - .callback = flecs_unit_quantity_monitor - }); - ecs_set_scope(world, old_scope); +ecs_http_server_t *flecs_wasm_rest_server; - /* 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\ - }); +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 - 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); +int ecs_app_run( + ecs_world_t *world, + ecs_app_desc_t *desc) +{ + ecs_app_desc = *desc; - #undef ECS_PRIMITIVE + /* 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); + } + } - 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) - }); + /* 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 + } - /* Set default child components */ - ecs_add_pair(world, ecs_id(EcsStruct), - EcsDefaultChildComponent, ecs_id(EcsMember)); + /* 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 + } - ecs_add_pair(world, ecs_id(EcsMember), - EcsDefaultChildComponent, ecs_id(EcsMember)); + return run_action(world, &ecs_app_desc); +} - ecs_add_pair(world, ecs_id(EcsEnum), - EcsDefaultChildComponent, EcsConstant); +int ecs_app_run_frame( + ecs_world_t *world, + const ecs_app_desc_t *desc) +{ + return frame_action(world, desc); +} - ecs_add_pair(world, ecs_id(EcsBitmask), - EcsDefaultChildComponent, EcsConstant); +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; + } - /* Relationship properties */ - ecs_add_id(world, EcsQuantity, EcsExclusive); - ecs_add_id(world, EcsQuantity, EcsTag); + run_action = callback; - /* 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"} - } - }); + return 0; +} - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsMetaType), - .members = { - {.name = (char*)"kind", .type = type_kind} - } - }); +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; + } - 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"} - } - }); + 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_Relationships.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(EcsPrimitive), + .entity = ecs_id(EcsComponent), .members = { - {.name = (char*)"kind", .type = primitive_kind} + {.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(EcsMember), + .entity = ecs_id(EcsDocDescription), .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"unit", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"offset", .type = ecs_id(ecs_i32_t)} + {.name = "value", .type = ecs_id(ecs_string_t)} } }); - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsArray), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_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_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsVector), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} - } - }); + ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsOpaque), - .members = { - { .name = (char*)"as_type", .type = ecs_id(ecs_entity_t) } - } - }); + ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); - ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_entity(world, { .name = "unit_translation" }), - .members = { - {.name = (char*)"factor", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"power", .type = ecs_id(ecs_i32_t)} - } - }); + ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all 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_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsUnit), - .members = { - {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, - {.name = (char*)"prefix", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"base", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"over", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"translation", .type = ut} - } - }); + ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); + ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); + ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); - ecs_struct_init(world, &(ecs_struct_desc_t){ - .entity = ecs_id(EcsUnitPrefix), - .members = { - {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, - {.name = (char*)"translation", .type = ut} - } - }); + ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property"); + ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property"); + ecs_doc_set_brief(world, EcsFinal, "Final relationship property"); + ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property"); + ecs_doc_set_brief(world, EcsTag, "Tag relationship property"); + ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property"); + ecs_doc_set_brief(world, EcsTraversable, "Traversable relationship property"); + ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property"); + ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property"); + ecs_doc_set_brief(world, EcsWith, "With relationship property"); + ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property"); + ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property"); + ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); + ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property"); + ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property"); + ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property"); + ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship"); + ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship"); + ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship"); + ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); + ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); + ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); + ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); + + ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); + ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); + ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); + ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); + ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); + ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); + ecs_doc_set_link(world, EcsTraversable, URL_ROOT "#traversable-property"); + ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); + ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); + ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); + ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); + ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship"); + ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship"); + + /* 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 meta/api.c - * @brief API for assigning values of runtime types with reflection. + * @file addons/doc.c + * @brief Doc addon. */ -#include -#ifdef FLECS_META +#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 -const char* flecs_meta_op_kind_str( - ecs_meta_type_op_kind_t kind) +void flecs_doc_set( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t kind, + const char *value) { - 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"; - default: return "<< invalid kind >>"; + 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); } } -/* Get current scope */ -static -ecs_meta_scope_t* flecs_meta_cursor_get_scope( - const ecs_meta_cursor_t *cursor) +void ecs_doc_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); - return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; -error: - return NULL; + flecs_doc_set(world, entity, EcsName, name); } -/* 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) +void ecs_doc_set_brief( + ecs_world_t *world, + ecs_entity_t entity, + const char *brief) { - 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]; + flecs_doc_set(world, entity, EcsDocBrief, brief); } -/* Get current operation for scope */ -static -ecs_meta_type_op_t* flecs_meta_cursor_get_op( - ecs_meta_scope_t *scope) +void ecs_doc_set_detail( + ecs_world_t *world, + ecs_entity_t entity, + const char *detail) { - ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, NULL); - return &scope->ops[scope->op_cur]; + flecs_doc_set(world, entity, EcsDocDetail, detail); } -/* Get component for type in current scope */ -static -const EcsComponent* get_ecs_component( - const ecs_world_t *world, - ecs_meta_scope_t *scope) +void ecs_doc_set_link( + ecs_world_t *world, + ecs_entity_t entity, + const char *link) { - 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; + flecs_doc_set(world, entity, EcsDocLink, link); } -/* Get size for type in current scope */ -static -ecs_size_t get_size( - const ecs_world_t *world, - ecs_meta_scope_t *scope) +void ecs_doc_set_color( + ecs_world_t *world, + ecs_entity_t entity, + const char *color) { - return get_ecs_component(world, scope)->size; + flecs_doc_set(world, entity, EcsDocColor, color); } -static -int32_t get_elem_count( - ecs_meta_scope_t *scope) +const char* ecs_doc_get_name( + const ecs_world_t *world, + ecs_entity_t entity) { - 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)); + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsName); + if (ptr) { + return ptr->value; + } else { + return ecs_get_name(world, entity); } - - 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 char* ecs_doc_get_brief( const ecs_world_t *world, - ecs_meta_scope_t *scope) + ecs_entity_t entity) { - 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; - } + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocBrief); + if (ptr) { + return ptr->value; + } else { + return NULL; } - - return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); } -static -int flecs_meta_cursor_push_type( +const char* ecs_doc_get_detail( const ecs_world_t *world, - ecs_meta_scope_t *scope, - ecs_entity_t type, - void *ptr) + ecs_entity_t entity) { - 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; + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocDetail); + if (ptr) { + return ptr->value; + } else { + return NULL; } - - 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 char* ecs_doc_get_link( const ecs_world_t *world, - ecs_entity_t type, - void *ptr) + ecs_entity_t entity) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ptr != NULL, 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; + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocLink); + if (ptr) { + return ptr->value; + } else { + return NULL; } - - return result; -error: - return (ecs_meta_cursor_t){ 0 }; } -void* ecs_meta_get_ptr( - ecs_meta_cursor_t *cursor) +const char* ecs_doc_get_color( + const ecs_world_t *world, + ecs_entity_t entity) { - return flecs_meta_cursor_get_ptr(cursor->world, - flecs_meta_cursor_get_scope(cursor)); + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocColor); + if (ptr) { + return ptr->value; + } else { + return NULL; + } } -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; - } +void FlecsDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsDoc); - if (scope->elem_cur >= get_elem_count(scope)) { - ecs_err("out of collection bounds (%d)", scope->elem_cur); - return -1; - } - return 0; - } + ecs_set_name_prefix(world, "EcsDoc"); - scope->op_cur += op->op_count; + 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); - if (scope->op_cur >= scope->op_count) { - ecs_err("out of bounds"); - return -1; - } + ecs_set_hooks(world, EcsDocDescription, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsDocDescription), + .copy = ecs_copy(EcsDocDescription), + .dtor = ecs_dtor(EcsDocDescription) + }); - return 0; + ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); + ecs_add_id(world, ecs_id(EcsDocDescription), EcsPrivate); + } -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; +#endif - 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; -} +/** + * @file addons/flecs_cpp.c + * @brief Utilities for C++ addon. + */ -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; - } +#include - ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); - scope = flecs_meta_cursor_restore_scope(cursor, scope); +/* Utilities for C++ API */ - ecs_hashmap_t *members = scope->members; - const ecs_world_t *world = cursor->world; +#ifdef FLECS_CPP - if (!members) { - ecs_err("cannot move to member '%s' for non-struct type", name); - return -1; - } +/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to + * a uniform identifier */ - 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; - } +#define ECS_CONST_PREFIX "const " +#define ECS_STRUCT_PREFIX "struct " +#define ECS_CLASS_PREFIX "class " +#define ECS_ENUM_PREFIX "enum " - scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); +#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)) - 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); - } +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 0; + return len; } -int ecs_meta_dotmember( - ecs_meta_cursor_t *cursor, - const char *name) +static +void ecs_cpp_trim_type_name( + char *typeName) { -#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; - } + ecs_size_t len = ecs_os_strlen(typeName); - if (dotcount) { - ecs_meta_push(cursor); - } + 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); - if (ecs_meta_member(cursor, token)) { - goto error; - } + while (typeName[len - 1] == ' ' || + typeName[len - 1] == '&' || + typeName[len - 1] == '*') + { + len --; + typeName[len] = '\0'; + } - if (!ptr[0]) { - break; + /* 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'; } - - ptr ++; /* Skip . */ - - dotcount ++; + len -= ECS_CONST_LEN; } - cur_scope = flecs_meta_cursor_get_scope(cursor); - if (dotcount) { - cur_scope->prev_depth = prev_depth; + /* 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; + } + } } +} - 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 +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; } -int ecs_meta_push( - ecs_meta_cursor_t *cursor) +char* ecs_cpp_get_symbol_name( + char *symbol_name, + const char *type_name, + size_t len) { - 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; + // Symbol is same as name, but with '::' replaced with '.' + ecs_os_strcpy(symbol_name, type_name); - if (cursor->depth == 0) { - if (!cursor->is_primitive_scope) { - if (op->kind > EcsOpScope) { - cursor->is_primitive_scope = true; - return 0; - } + 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; } } - 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); + symbol_name[i] = '\0'; - /* 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 - }; + return symbol_name; +} - /* With 'is_inline_array' set to true we ensure that we can never push - * the same inline array twice */ - return 0; +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; +} - /* 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; - } +static +const char* flecs_cpp_func_max( + const char *a, + const char *b) +{ + if (a > b) return a; + return b; +} - /* 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; - } +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; +} - const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; +// 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; } - /* Vector push */ - case EcsOpVector: { - next_scope->vector = ptr; - if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { - goto error; + char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); + if (path) { + const char *ptr = strrchr(type_name, ':'); + ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); + if (ptr) { + ptr --; + ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); + ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); + if (name_path_len <= ecs_os_strlen(path)) { + if (!ecs_os_strncmp(type_name, path, name_path_len)) { + type_name = &type_name[name_path_len + 2]; + } + } } - - const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; } + ecs_os_free(path); - /* 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; + return type_name; +} - /* Push the element type of the vector type */ - if (flecs_meta_cursor_push_type( - world, next_scope, vt->type, NULL) != 0) - { - goto error; +// 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); } - - /* 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; + ecs_os_free(path); +#endif } - /* 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; + 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); + } } - - /* Arrays are always a fixed size */ - next_scope->is_empty_scope = false; - next_scope->is_collection = true; - break; + } + } else { + /* Ensure that the entity id valid */ + if (!ecs_is_alive(world, id)) { + ecs_ensure(world, id); } - /* 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; - } + /* 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, "::", "::"); + } - /* 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; + /* If a component was already registered with this id but with a + * different size, the ecs_component_init function will fail. */ - default: - break; - } + /* 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); +} - next_scope->ptr = ptr; - next_scope->opaque = type_ptr; - break; - } +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; - default: { - char *path = ecs_get_fullpath(world, scope->type); - ecs_err("invalid push for type '%s'", path); - ecs_os_free(path); - goto error; + /* 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 (scope->is_collection && !scope->opaque) { - next_scope->ptr = ECS_OFFSET(next_scope->ptr, - scope->elem_cur * get_size(world, scope)); + if (existing_out) { + *existing_out = existing; } - return 0; -error: - return -1; + return ent; } -int ecs_meta_pop( - ecs_meta_cursor_t *cursor) +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) { - if (cursor->is_primitive_scope) { - cursor->is_primitive_scope = false; - return 0; - } + char *existing_name = NULL; + if (existing_out) *existing_out = false; - 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; + // 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_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); + 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); - /* 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); - } + 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 { - /* Make sure that this was an inline array */ - ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); + entity = ecs_entity(world, { + .id = s_id, + .name = name, + .sep = "::", + .root_sep = "::", + .symbol = symbol, + .use_low_id = true + }); } - 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_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(existing_name); -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; + return entity; } -ecs_entity_t ecs_meta_get_unit( - const ecs_meta_cursor_t *cursor) +void ecs_cpp_enum_init( + ecs_world_t *world, + ecs_entity_t id) { - 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->unit; + (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 } -const char* ecs_meta_get_member( - const ecs_meta_cursor_t *cursor) +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_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_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); -/* Utilities for type conversions and bounds checking */ -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}, - [EcsOpEnum] = {INT32_MIN, INT32_MAX}, - [EcsOpBitmask] = {0, INT32_MAX} -}; + 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 ++; + } + } -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}, - [EcsOpEnum] = {0, INT32_MAX}, - [EcsOpBitmask] = {0, UINT32_MAX} -}; + 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); -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}, - [EcsOpEnum] = {INT32_MIN, INT32_MAX}, - [EcsOpBitmask] = {0, UINT32_MAX} -}; + #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 -#define set_T(T, ptr, value)\ - ((T*)ptr)[0] = ((T)value) +#ifdef FLECS_META + ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)), + sizeof(ecs_i32_t), &value); +#endif -#define case_T(kind, T, dst, src)\ -case kind:\ - set_T(T, dst, src);\ - break + flecs_resume_readonly(world, &readonly_state); -#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 + ecs_trace("#[green]constant#[reset] %s.%s created with value %d", + ecs_get_name(world, parent), name, value); -#define cases_T_float(dst, src)\ - case_T(EcsOpF32, ecs_f32_t, dst, src);\ - case_T(EcsOpF64, ecs_f64_t, dst, src) + return id; +} -#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) +static int32_t flecs_reset_count = 0; -#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(EcsOpBitmask, ecs_u32_t, dst, src, bounds) +int32_t ecs_cpp_reset_count_get(void) { + return flecs_reset_count; +} -#define cases_T_bool(dst, src)\ -case EcsOpBool:\ - set_T(ecs_bool_t, dst, value != 0);\ - break +int32_t ecs_cpp_reset_count_inc(void) { + return ++flecs_reset_count; +} -static -void flecs_meta_conversion_error( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - const char *from) +#ifdef FLECS_META +const ecs_member_t* ecs_cpp_last_member( + const ecs_world_t *world, + ecs_entity_t type) { - 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); + 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 -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); +#endif - 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 */ - default: - flecs_meta_conversion_error(cursor, op, "bool"); - return -1; - } +/** + * @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. + */ - return 0; -} -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); +#ifdef FLECS_HTTP - 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 */ - default: - flecs_meta_conversion_error(cursor, op, "char"); - return -1; - } +#ifdef ECS_TARGET_MSVC +#pragma comment(lib, "Ws2_32.lib") +#endif - return 0; -} +#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; -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); +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL (0) +#endif - 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 */ - default: { - if(!value) return ecs_meta_set_null(cursor); - flecs_meta_conversion_error(cursor, op, "int"); - return -1; - } - } +#endif - return 0; -} +/* Max length of request method */ +#define ECS_HTTP_METHOD_LEN_MAX (8) -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); +/* Timeout (s) before connection purge */ +#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) - 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 */ - default: - if(!value) return ecs_meta_set_null(cursor); - flecs_meta_conversion_error(cursor, op, "uint"); - return -1; - } +/* Number of dequeues before purging */ +#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) - return 0; -} +/* Number of retries receiving request */ +#define ECS_HTTP_REQUEST_RECV_RETRY (10) -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); +/* Minimum interval between dequeueing requests (ms) */ +#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) - switch(op->kind) { - cases_T_bool(ptr, value); - 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_world_t*)cursor->world, (ecs_entity_t)value); - break; - } - } - /* fall through */ - default: - flecs_meta_conversion_error(cursor, op, "float"); - return -1; +/* 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) + +/* Cache invalidation timeout (s) */ +#define ECS_HTTP_CACHE_TIMEOUT ((ecs_ftime_t)1.0) + +/* Cache entry purge timeout (s) */ +#define ECS_HTTP_CACHE_PURGE_TIMEOUT ((ecs_ftime_t)10.0) + +/* 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; + 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_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 0; + return ret; } -int ecs_meta_set_value( - ecs_meta_cursor_t *cursor, - const ecs_value_t *value) +static +void http_sock_set_timeout( + ecs_http_socket_t sock, + int32_t timeout_ms) { - 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; + 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)); } +} - 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); - 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); +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)); } +} -error: - return -1; +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 flecs_meta_add_bitmask_constant( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - void *out, - const char *value) +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(op->type != 0, ECS_INTERNAL_ERROR, NULL); + 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 +} - if (!ecs_os_strcmp(value, "0")) { - return 0; - } +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 +} - 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; - } +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 +} - 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; - } +#if defined(ECS_TARGET_WINDOWS) +#define HTTP_SOCKET_INVALID INVALID_SOCKET +#else +#define HTTP_SOCKET_INVALID (-1) +#endif - *(ecs_u32_t*)out |= v[0]; +static +void http_close( + ecs_http_socket_t *sock) +{ + ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); - return 0; +#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 -int flecs_meta_parse_bitmask( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - void *out, - const char *value) +ecs_http_socket_t http_accept( + ecs_http_socket_t sock, + struct sockaddr* addr, + ecs_size_t *addr_len) { - char token[ECS_MAX_TOKEN_SIZE]; + 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; +} - const char *prev = value, *ptr = value; +static +void http_reply_fini(ecs_http_reply_t* reply) { + ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(reply->body.content); +} - *(ecs_u32_t*)out = 0; +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); +} - 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; - } +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; - ptr ++; - prev = ptr; + if (http_socket_is_valid(conn->sock)) { + http_close(&conn->sock); } - if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { - return -1; - } + flecs_sparse_remove_t(&conn->pub.server->connections, + ecs_http_connection_impl_t, conn_id); +} - return 0; +// 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 -int flecs_meta_cursor_lookup( - ecs_meta_cursor_t *cursor, - const char *value, - ecs_entity_t *out) +void http_decode_url_str( + char *str) { - if (ecs_os_strcmp(value, "0")) { - if (cursor->lookup_action) { - *out = cursor->lookup_action( - cursor->world, value, - cursor->lookup_ctx); + 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 { - *out = ecs_lookup_path(cursor->world, 0, value); - } - if (!*out) { - ecs_err("unresolved entity identifier '%s'", value); - return -1; + dst[0] = ptr[0]; + dst ++; } } - return 0; + dst[0] = '\0'; } -int ecs_meta_set_string( - ecs_meta_cursor_t *cursor, - const char *value) +static +void http_parse_method( + ecs_http_fragment_t *frag) { - 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); + 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); +} - 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); - return -1; - } - 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, atol(value)); - break; - case EcsOpIPtr: - case EcsOpUPtr: - set_T(ecs_iptr_t, ptr, atol(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); - return -1; - } - - 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); - return -1; - } - - set_T(ecs_i32_t, ptr, v[0]); - break; - } - case EcsOpBitmask: - if (flecs_meta_parse_bitmask(cursor, op, ptr, value) != 0) { - return -1; - } - break; - case EcsOpEntity: { - ecs_entity_t e = 0; - if (flecs_meta_cursor_lookup(cursor, value, &e)) { - return -1; - } - set_T(ecs_entity_t, ptr, e); - break; - } - case EcsOpPop: - ecs_err("excess element '%s' in scope", value); - return -1; - 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)) { - return -1; - } - opaque->assign_entity(ptr, (ecs_world_t*)cursor->world, e); - break; - } - } - /* fall through */ - default: - ecs_err("unsupported conversion from string '%s' to '%s'", - value, flecs_meta_op_kind_str(op->kind)); - return -1; - } - - return 0; +static +bool http_header_writable( + ecs_http_fragment_t *frag) +{ + return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; } -int ecs_meta_set_string_literal( - ecs_meta_cursor_t *cursor, - const char *value) +static +void http_header_buf_reset( + ecs_http_fragment_t *frag) { - 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); + frag->header_buf[0] = '\0'; + frag->header_buf_ptr = frag->header_buf; +} - ecs_size_t len = ecs_os_strlen(value); - if (value[0] != '\"' || value[len - 1] != '\"') { - ecs_err("invalid string literal '%s'", value); - return -1; +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'; } +} - switch(op->kind) { - case EcsOpChar: - set_T(ecs_char_t, ptr, value[1]); - break; - - default: - case EcsOpEntity: - case EcsOpString: - case EcsOpOpaque: - len -= 2; +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)); +} - char *result = ecs_os_malloc(len + 1); - ecs_os_memcpy(result, value + 1, len); - result[len] = '\0'; +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; - if (ecs_meta_set_string(cursor, result)) { - ecs_os_free(result); - return -1; - } + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; - ecs_os_free(result); - break; + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); } - return 0; + return ecs_os_memcmp(type_1->array, type_2->array, count_1); } -int ecs_meta_set_entity( - ecs_meta_cursor_t *cursor, - ecs_entity_t value) +static +ecs_http_request_entry_t* http_find_request_entry( + ecs_http_server_t *srv, + const char *array, + int32_t count) { - 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 EcsOpOpaque: { - const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); - if (opaque && opaque->assign_entity) { - opaque->assign_entity(ptr, (ecs_world_t*)cursor->world, value); - break; - } - } - /* fall through */ - default: - flecs_meta_conversion_error(cursor, op, "entity"); - return -1; - } + ecs_http_request_key_t key; + key.array = array; + key.count = count; - return 0; -} + ecs_time_t t = {0, 0}; + ecs_http_request_entry_t *entry = flecs_hashmap_get( + &srv->request_cache, &key, ecs_http_request_entry_t); -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; + if (entry) { + ecs_ftime_t tf = (ecs_ftime_t)ecs_time_measure(&t); + if ((tf - entry->time) < ECS_HTTP_CACHE_TIMEOUT) { + return entry; } } - /* fall through */ - default: - flecs_meta_conversion_error(cursor, op, "null"); - return -1; - } - - return 0; + return NULL; } -bool ecs_meta_get_bool( - const ecs_meta_cursor_t *cursor) +static +void http_insert_request_entry( + ecs_http_server_t *srv, + ecs_http_request_impl_t *req, + ecs_http_reply_t *reply) { - 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_f32_t*)ptr != 0; - case EcsOpF64: return *(ecs_f64_t*)ptr != 0; - 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; - default: ecs_throw(ECS_INVALID_PARAMETER, - "invalid element for bool"); + int32_t content_length = ecs_strbuf_written(&reply->body); + if (!content_length) { + return; } -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; - default: ecs_throw(ECS_INVALID_PARAMETER, - "invalid element for char"); + 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); } -error: - return 0; + 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); + ecs_strbuf_appendstrn(&reply->body, + entry->content, entry->content_length); } -int64_t ecs_meta_get_int( - const ecs_meta_cursor_t *cursor) +static +char* http_decode_request( + ecs_http_request_impl_t *req, + ecs_http_fragment_t *frag) { - 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; - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return *(ecs_char_t*)ptr; - case EcsOpByte: return *(ecs_u8_t*)ptr; - case EcsOpI16: return *(ecs_i16_t*)ptr; - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return *(ecs_i32_t*)ptr; - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return *(ecs_i64_t*)ptr; - case EcsOpU64: return flecs_uto(int64_t, *(ecs_u64_t*)ptr); - case EcsOpIPtr: return *(ecs_iptr_t*)ptr; - case EcsOpUPtr: return flecs_uto(int64_t, *(ecs_uptr_t*)ptr); - case EcsOpF32: return (int64_t)*(ecs_f32_t*)ptr; - case EcsOpF64: return (int64_t)*(ecs_f64_t*)ptr; - case EcsOpString: return atoi(*(const char**)ptr); - case EcsOpEnum: return *(ecs_i32_t*)ptr; - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: - ecs_throw(ECS_INVALID_PARAMETER, - "invalid conversion from entity to int"); - break; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); + ecs_os_zeromem(req); + + char *res = ecs_strbuf_get(&frag->buf); + if (!res) { + return NULL; } -error: - return 0; -} + req->pub.method = frag->method; + req->pub.path = res + 1; + http_decode_url_str(req->pub.path); -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, *(ecs_i8_t*)ptr); - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return flecs_ito(uint64_t, *(ecs_char_t*)ptr); - case EcsOpByte: return flecs_ito(uint64_t, *(ecs_u8_t*)ptr); - case EcsOpI16: return flecs_ito(uint64_t, *(ecs_i16_t*)ptr); - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); - case EcsOpU64: return *(ecs_u64_t*)ptr; - case EcsOpIPtr: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); - case EcsOpUPtr: return *(ecs_uptr_t*)ptr; - case EcsOpF32: return flecs_ito(uint64_t, *(ecs_f32_t*)ptr); - case EcsOpF64: return flecs_ito(uint64_t, *(ecs_f64_t*)ptr); - case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); - case EcsOpEnum: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: return *(ecs_entity_t*)ptr; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); + if (frag->body_offset) { + req->pub.body = &res[frag->body_offset]; } -error: - return 0; -} - -static -double flecs_meta_to_float( - ecs_meta_type_op_kind_t kind, - const void *ptr) -{ - switch(kind) { - case EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return *(ecs_i8_t*)ptr; - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return *(ecs_char_t*)ptr; - case EcsOpByte: return *(ecs_u8_t*)ptr; - case EcsOpI16: return *(ecs_i16_t*)ptr; - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return *(ecs_i32_t*)ptr; - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return (double)*(ecs_i64_t*)ptr; - case EcsOpU64: return (double)*(ecs_u64_t*)ptr; - case EcsOpIPtr: return (double)*(ecs_iptr_t*)ptr; - case EcsOpUPtr: return (double)*(ecs_uptr_t*)ptr; - case EcsOpF32: return (double)*(ecs_f32_t*)ptr; - case EcsOpF64: return *(ecs_f64_t*)ptr; - case EcsOpString: return atof(*(const char**)ptr); - case EcsOpEnum: return *(ecs_i32_t*)ptr; - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: - ecs_throw(ECS_INVALID_PARAMETER, - "invalid conversion from entity to float"); - break; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); + 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]]; } -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; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); + 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)); } -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; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); - } -error: - return 0; -} + req->pub.header_count = frag->header_count; + req->pub.param_count = frag->param_count; + req->res = res; + req->req_len = frag->header_offsets[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); + return res; } -#endif - -/** - * @file expr/serialize.c - * @brief Serialize (component) values to flecs string format. - */ - +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; -#ifdef FLECS_EXPR + ecs_os_mutex_lock(srv->lock); + bool is_alive = conn->pub.id == conn_id; -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); + 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; -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); + /* 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; + } + } -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); + 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); + } + } -static -ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { - return kind - EcsOpPrimitive; + ecs_os_mutex_unlock(srv->lock); + return NULL; } -/* 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) +bool http_parse_request( + ecs_http_fragment_t *frag, + const char* req_frag, + ecs_size_t req_frag_len) { - switch(kind) { - case EcsBool: - if (*(bool*)base) { - ecs_strbuf_appendlit(str, "true"); - } else { - ecs_strbuf_appendlit(str, "false"); - } - break; - case EcsChar: { - char chbuf[3]; - char ch = *(char*)base; - if (ch) { - ecs_chresc(chbuf, *(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, *(uint8_t*)base)); - break; - case EcsU8: - ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint8_t*)base)); - break; - case EcsU16: - ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint16_t*)base)); - break; - case EcsU32: - ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint32_t*)base)); - break; - case EcsU64: - ecs_strbuf_append(str, "%llu", *(uint64_t*)base); - break; - case EcsI8: - ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int8_t*)base)); - break; - case EcsI16: - ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int16_t*)base)); - break; - case EcsI32: - ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int32_t*)base)); - break; - case EcsI64: - ecs_strbuf_appendint(str, *(int64_t*)base); - break; - case EcsF32: - ecs_strbuf_appendflt(str, (double)*(float*)base, 0); - break; - case EcsF64: - ecs_strbuf_appendflt(str, *(double*)base, 0); - break; - case EcsIPtr: - ecs_strbuf_appendint(str, flecs_ito(int64_t, *(intptr_t*)base)); - break; - case EcsUPtr: - ecs_strbuf_append(str, "%u", *(uintptr_t*)base); - break; - case EcsString: { - char *value = *(char**)base; - if (value) { - if (!is_expr) { - ecs_strbuf_appendstr(str, value); + 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->buf.max = ECS_HTTP_METHOD_LEN_MAX; + frag->state = HttpFragStateMethod; + frag->header_buf_ptr = frag->header_buf; + + /* fall through */ + case HttpFragStateMethod: + if (c == ' ') { + http_parse_method(frag); + frag->state = HttpFragStatePath; + frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; } 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, '"'); + 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 { - 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_zerocpy(str, out); + ecs_strbuf_appendch(&frag->buf, c); } } - } else { - ecs_strbuf_appendlit(str, "null"); - } - break; - } - case EcsEntity: { - ecs_entity_t e = *(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 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; } - break; - } - default: - ecs_err("invalid primitive kind"); - return -1; } - return 0; + if (frag->state == HttpFragStateDone) { + return true; + } else { + return false; + } } -/* 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) +ecs_http_send_request_t* http_send_queue_post( + ecs_http_server_t *srv) { - const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); - ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); - - int32_t val = *(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; + /* 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; } - ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant)); + /* Don't enqueue new requests if server is shutting down */ + if (!srv->should_run) { + return NULL; + } - return 0; -error: - return -1; + /* Return element at end of the queue */ + ecs_http_send_request_t *result = &sq->requests[sq->head]; + sq->head = next; + return result; } -/* 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) +ecs_http_send_request_t* http_send_queue_get( + ecs_http_server_t *srv) { - const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); - ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); - uint32_t value = *(uint32_t*)ptr; + ecs_os_mutex_lock(srv->lock); + ecs_http_send_queue_t *sq = &srv->send_queue; + if (sq->tail == sq->head) { + return NULL; + } - ecs_strbuf_list_push(str, "", "|"); + 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; +} - /* 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; - } - } +static +void* http_server_send_queue(void* arg) { + ecs_http_server_t *srv = arg; + int32_t wait_ms = srv->send_queue.wait_ms; - 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; - } + /* 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 (!count) { - ecs_strbuf_list_appendstr(str, "0"); - } + if (http_socket_is_valid(sock)) { + bool error = false; - ecs_strbuf_list_pop(str, ""); + http_sock_nonblock(sock, false); - return 0; -error: - return -1; + /* 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; } -/* 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) +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_list_push(str, "[", ", "); + 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"); - const void *ptr = base; + if (content_type) { + ecs_strbuf_appendlit(hdrs, "Content-Type: "); + ecs_strbuf_appendstr(hdrs, content_type); + ecs_strbuf_appendlit(hdrs, "\r\n"); + } - 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); + 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_list_pop(str, "]"); + 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"); + } - return 0; + ecs_strbuf_mergebuff(hdrs, extra_headers); + + ecs_strbuf_appendlit(hdrs, "\r\n"); } 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) +void http_send_reply( + ecs_http_connection_impl_t* conn, + ecs_http_reply_t* reply, + bool preflight) { - 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_strbuf_t hdrs = ECS_STRBUF_INIT; + char *content = ecs_strbuf_get(&reply->body); + int32_t content_length = reply->body.length - 1; - 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); -} + /* Use asynchronous send queue for outgoing data so send operations won't + * hold up main thread */ + ecs_http_send_request_t *req = NULL; -/* 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); + 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); + } + } - return expr_ser_type_elements( - world, a->type, ptr, a->count, str, true); -} + http_append_send_headers(&hdrs, reply->code, reply->status, + reply->content_type, &reply->headers, content_length, preflight); + char *headers = ecs_strbuf_get(&hdrs); + ecs_size_t headers_length = ecs_strbuf_written(&hdrs); -/* 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); + 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; + } - int32_t count = ecs_vec_count(value); - void *array = ecs_vec_first(value); + /* 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; - /* Serialize contiguous buffer of vector */ - return expr_ser_type_elements(world, v->type, array, count, str, false); + /* Take ownership of values */ + reply->body.content = NULL; + conn->sock = HTTP_SOCKET_INVALID; } -/* 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) +void http_recv_connection( + ecs_http_server_t *srv, + ecs_http_connection_impl_t *conn, + uint64_t conn_id, + ecs_http_socket_t sock) { - 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; - default: - 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; - } - - 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]; + ecs_size_t bytes_read; + char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + ecs_http_fragment_t frag = {0}; + int32_t retries = 0; - if (in_array <= 0) { - if (op->name) { - ecs_strbuf_list_next(str); - ecs_strbuf_append(str, "%s: ", op->name); + 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; } - 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; - } + 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 = 200; + reply.content_type = NULL; + 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); - i += op->op_count - 1; - continue; + /* Lock was transferred from enqueue_request */ + ecs_os_mutex_unlock(srv->lock); + } + } + } else { + ecs_os_linc(&ecs_http_request_invalid_count); } } - switch(op->kind) { - case EcsOpPush: - ecs_strbuf_list_push(str, "{", ", "); - in_array --; - break; - case EcsOpPop: - ecs_strbuf_list_pop(str, "}"); - in_array ++; - break; - default: - if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) { - goto error; - } - break; - } + 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); } - return 0; -error: - return -1; +done: + ecs_strbuf_reset(&frag.buf); } -/* 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); -} +typedef struct { + ecs_http_connection_impl_t *conn; + uint64_t id; +} http_conn_res_t; -int ecs_ptr_to_expr_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *ptr, - ecs_strbuf_t *buf_out) +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) { - 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; - } + http_sock_set_timeout(sock_conn, 100); + http_sock_keep_alive(sock_conn); + http_sock_nonblock(sock_conn, true); - return 0; -error: - return -1; -} + /* 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* ecs_ptr_to_expr( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr) -{ - ecs_strbuf_t str = ECS_STRBUF_INIT; + char *remote_host = conn->pub.host; + char *remote_port = conn->pub.port; - if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; + /* 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"); } - return ecs_strbuf_get(&str); + 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 }; } -int ecs_ptr_to_str_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *ptr, - ecs_strbuf_t *buf_out) +static +void http_accept_connections( + ecs_http_server_t* srv, + const struct sockaddr* addr, + ecs_size_t addr_len) { - 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; +#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 - return 0; -error: - return -1; -} + /* Resolve name + port (used for logging) */ + char addr_host[256]; + char addr_port[20]; -char* ecs_ptr_to_str( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr) -{ - ecs_strbuf_t str = ECS_STRBUF_INIT; + ecs_http_socket_t sock = HTTP_SOCKET_INVALID; + ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); - if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { - ecs_strbuf_reset(&str); - return 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"); } - return ecs_strbuf_get(&str); -} + ecs_os_mutex_lock(srv->lock); + if (srv->should_run) { + ecs_dbg_2("http: initializing connection socket"); -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); -} + 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; + } -#endif + 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)); + } -/** - * @file expr/vars.c - * @brief Utilities for variable substitution in flecs string expressions. - */ + 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; + } -#ifdef FLECS_EXPR + http_sock_set_timeout(sock, 1000); -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; -} + srv->sock = sock; -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); + result = listen(srv->sock, SOMAXCONN); + if (result) { + ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", + SOMAXCONN, ecs_os_strerror(errno)); } - flecs_strfree(&world->allocator, var->name); + + 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); - ecs_vec_fini_t(&world->allocator, &scope->vars, ecs_expr_var_t); - flecs_name_index_fini(&scope->var_index); -} + struct sockaddr_storage remote_addr; + ecs_size_t remote_addr_len = 0; -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; -} + 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); -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; + if (!http_socket_is_valid(sock_conn)) { + if (srv->should_run) { + ecs_dbg("http: connection attempt failed: %s", + ecs_os_strerror(errno)); + } + continue; } - } 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; + 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); } - 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; +done: + ecs_os_mutex_lock(srv->lock); + if (http_socket_is_valid(sock) && errno != EBADF) { + http_close(&sock); + srv->sock = sock; } - var->value.type = type; - var->name = flecs_strdup(&vars->world->allocator, name); - var->owned = true; + ecs_os_mutex_unlock(srv->lock); - 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_trace("http: no longer accepting connections on '%s:%s'", + addr_host, addr_port); } -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; +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 (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; + if (!srv->ipaddr) { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } - 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: + http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); return NULL; } static -ecs_expr_var_t* flecs_vars_scope_lookup( - ecs_expr_var_scope_t *scope, - const char *name) +void http_do_request( + ecs_http_server_t *srv, + ecs_http_reply_t *reply, + const ecs_http_request_impl_t *req) { - 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); + 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); } - 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) +static +void http_handle_request( + ecs_http_server_t *srv, + ecs_http_request_impl_t *req) { - return flecs_vars_scope_lookup(vars->cur, name); -} - -#endif + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + ecs_http_connection_impl_t *conn = + (ecs_http_connection_impl_t*)req->pub.conn; -/** - * @file expr/strutil.c - * @brief String parsing utilities. - */ + 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); + } -#ifdef FLECS_EXPR + 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 */ + } -#include + http_reply_fini(&reply); + http_request_fini(req); + http_connection_free(conn); +} -char* ecs_chresc( - char *out, - char in, - char delimiter) +static +void http_purge_request_cache( + ecs_http_server_t *srv, + bool fini) { - 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; - default: - if (in == delimiter) { - *bptr++ = '\\'; - *bptr = delimiter; - } else { - *bptr = in; + 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) > ECS_HTTP_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); + } } - break; } - *(++bptr) = '\0'; - - return bptr; + if (fini) { + flecs_hashmap_fini(&srv->request_cache); + } } -const char* ecs_chrparse( - const char *in, - char *out) +static +int32_t http_dequeue_requests( + ecs_http_server_t *srv, + double delta_time) { - const char *result = in + 1; - char ch; + ecs_os_mutex_lock(srv->lock); - if (in[0] == '\\') { - result ++; + 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); + } - 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; + 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); } - } else { - ch = in[0]; } - if (out) { - *out = ch; - } + http_purge_request_cache(srv, false); + ecs_os_mutex_unlock(srv->lock); - return result; -error: - return NULL; + return request_count - 1; } -ecs_size_t ecs_stresc( - char *out, - ecs_size_t n, - char delimiter, - const char *in) +const char* ecs_http_get_header( + const ecs_http_request_t* req, + const char* name) { - 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++; + 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 written; -error: - return 0; + return NULL; } -char* ecs_astresc( - char delimiter, - const char *in) +const char* ecs_http_get_param( + const ecs_http_request_t* req, + const char* name) { - if (!in) { - return NULL; + 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; + } } - - 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; + return NULL; } -static -const char* flecs_parse_var_name( - const char *ptr, - char *token_out) +ecs_http_server_t* ecs_http_server_init( + const ecs_http_server_desc_t *desc) { - char ch, *bptr = token_out; + ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, + "missing OS API implementation"); - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } + ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); + srv->lock = ecs_os_mutex_new(); + srv->sock = HTTP_SOCKET_INVALID; - if (isalpha(ch) || isdigit(ch) || ch == '_') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - break; - } - } + srv->should_run = false; + srv->initialized = true; - if (bptr == token_out) { - goto error; + 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; } - *bptr = '\0'; + 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); - return ptr; + /* 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; } -static -const char* flecs_parse_interpolated_str( - const char *ptr, - char *token_out) +void ecs_http_server_fini( + ecs_http_server_t* srv) { - char ch, *bptr = token_out; + 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); +} - while ((ch = *ptr)) { - if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { - goto error; - } +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); - if (ch == '\\') { - if (ptr[1] == '}') { - *bptr = '}'; - bptr ++; - ptr += 2; - continue; - } - } + srv->should_run = true; - if (ch != '}') { - *bptr = ch; - bptr ++; - ptr ++; - } else { - ptr ++; - break; - } - } + ecs_dbg("http: starting server thread"); - if (bptr == token_out) { + srv->thread = ecs_os_thread_new(http_server_thread, srv); + if (!srv->thread) { goto error; } - *bptr = '\0'; + srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); + if (!srv->send_queue.thread) { + goto error; + } - return ptr; + return 0; error: - return NULL; + return -1; } -char* ecs_interpolate_string( - ecs_world_t *world, - const char *str, - const ecs_vars_t *vars) +void ecs_http_server_stop( + ecs_http_server_t* srv) { - char token[ECS_MAX_TOKEN_SIZE]; - ecs_strbuf_t result = ECS_STRBUF_INIT; - const char *ptr; - char ch; + 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); - 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 --; - } + /* Stop server thread */ + ecs_dbg("http: shutting down server thread"); - 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_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_expr_var_t *var = ecs_vars_lookup(vars, token); - if (!var) { - ecs_parser_error(NULL, str, ptr - str, - "unresolved variable '%s'", token); - goto error; - } + ecs_os_thread_join(srv->thread); + ecs_os_thread_join(srv->send_queue.thread); + ecs_trace("http: server threads shut down"); - if (ecs_ptr_to_str_buf( - world, var->value.type, var->value.ptr, &result)) - { - goto error; - } + /* 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)); + } - 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; - } + /* 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_parse_expr_desc_t expr_desc = { .vars = (ecs_vars_t*)vars }; - ecs_value_t expr_result = {0}; - if (!ecs_parse_expr(world, token, &expr_result, &expr_desc)) { - goto error; - } + ecs_assert(flecs_sparse_count(&srv->connections) == 1, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_sparse_count(&srv->requests) == 1, + ECS_INTERNAL_ERROR, NULL); - if (ecs_ptr_to_str_buf( - world, expr_result.type, expr_result.ptr, &result)) - { - goto error; - } + srv->thread = 0; +error: + return; +} - ecs_value_free(world, expr_result.type, expr_result.ptr); +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; - ptr --; - } else { - ecs_strbuf_appendch(&result, ch); - } + 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; } - return ecs_strbuf_get(&result); error: - return NULL; + return; } -void ecs_iter_to_vars( - const ecs_iter_t *it, - ecs_vars_t *vars, - int offset) +int ecs_http_server_http_request( + ecs_http_server_t* srv, + const char *req, + ecs_size_t len, + ecs_http_reply_t *reply_out) { - 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); + if (!len) { + len = ecs_os_strlen(req); + } - /* 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]; - } + ecs_http_fragment_t frag = {0}; + if (!http_parse_request(&frag, req, len)) { + ecs_strbuf_reset(&frag.buf); + reply_out->code = 400; + return -1; } - /* 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; - } - } + ecs_http_request_impl_t request; + char *res = http_decode_request(&request, &frag); + if (!res) { + reply_out->code = 400; + return -1; } - /* 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; - } + http_do_request(srv, reply_out, &request); + ecs_os_free(res); - 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; - } - } - } + return (reply_out->code >= 400) ? -1 : 0; +} -error: - return; +int ecs_http_server_request( + ecs_http_server_t* srv, + const char *method, + const char *req, + ecs_http_reply_t *reply_out) +{ + ecs_strbuf_t reqbuf = ECS_STRBUF_INIT; + ecs_strbuf_appendstr_zerocpy_const(&reqbuf, method); + ecs_strbuf_appendlit(&reqbuf, " "); + ecs_strbuf_appendstr_zerocpy_const(&reqbuf, req); + ecs_strbuf_appendlit(&reqbuf, " HTTP/1.1\r\n\r\n"); + int32_t len = ecs_strbuf_written(&reqbuf); + char *reqstr = ecs_strbuf_get(&reqbuf); + int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); + ecs_os_free(reqstr); + return result; +} + +void* ecs_http_server_ctx( + ecs_http_server_t* srv) +{ + return srv->ctx; } #endif /** - * @file expr/deserialize.c - * @brief Deserialize flecs string format into (component) values. + * @file addons/journal.c + * @brief Journal addon. */ -#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; +#ifdef FLECS_JOURNAL static -const char* flecs_parse_expr( +char* flecs_journal_entitystr( 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_entity_t entity) { - 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); + char *path; + const char *_path = ecs_get_symbol(world, entity); + if (_path && !strchr(_path, '.')) { + path = ecs_asprintf("#[blue]%s", _path); } 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 ++; + 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 result; + return path; } static -const char* flecs_str_to_expr_oper( - const char *str, - ecs_expr_oper_t *op) +char* flecs_journal_idstr( + ecs_world_t *world, + ecs_id_t id) { - 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; + 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); } - - *op = EcsExprOperUnknown; - return NULL; } -const char *ecs_parse_expr_token( - const char *name, - const char *expr, - const char *ptr, - char *token) -{ - const char *start = ptr; - char *token_ptr = token; +static int flecs_journal_sp = 0; - if (ptr[0] == '/') { - char ch; - if (ptr[1] == '/') { - // Single line comment - for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} - return ptr; - } else if (ptr[1] == '*') { - // Multi line comment - for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { - if (ch == '*' && ptr[1] == '/') { - return ptr + 2; - } - } +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 ++; - ecs_parser_error(name, expr, ptr - expr, - "missing */ for multiline comment"); - return NULL; - } + if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { + return; } - 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; - } + char *path = NULL; + char *var_id = NULL; + if (entity) { + path = ecs_get_fullpath(world, entity); + var_id = flecs_journal_entitystr(world, entity); } - while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr, 0))) { - if (ptr[0] == '|' && ptr[1] != '|') { - token_ptr = &token_ptr[ptr - start]; - token_ptr[0] = '|'; - token_ptr[1] = '\0'; - token_ptr ++; - ptr ++; - start = ptr; - } else { - break; - } + 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); } - - 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 ++; + 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); } - ecs_strbuf_appendch(&str, ch); - ptr ++; } - - if (ch != '`') { - ecs_parser_error(name, expr, ptr - expr, - "missing '`' to close multiline string"); - goto error; + 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); + } } - char *strval = ecs_strbuf_get(&str); - if (ecs_meta_set_string(cur, strval) != 0) { - goto error; + 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(strval); - - return ptr + 1; -error: - return NULL; + ecs_os_free(var_id); + ecs_os_free(path); + ecs_log_push(); } -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; +void flecs_journal_end(void) { + flecs_journal_sp --; + ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_log_pop(); } -/* 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){0}; - } +#endif - dot[0] = '\0'; +/** + * @file addons/log.c + * @brief Log addon. + */ - 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}; - } +#ifdef FLECS_LOG - return (ecs_value_t){ - .ptr = ecs_meta_get_ptr(&cur), - .type = ecs_meta_get_type(&cur) - }; -} +#include -/* 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) +void flecs_colorize_buf( + char *msg, + bool enable_colors, + ecs_strbuf_t *buf) { - /* String literal */ - if (ptr[0] == '"' || ptr[0] == '`') { - if (input_type == ecs_id(ecs_char_t)) { - return input_type; - } - return ecs_id(ecs_string_t); - } + ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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); - } - } + char *ptr, ch, prev = '\0'; + bool isNum = false; + char isStr = '\0'; + bool isVar = false; + bool overrideColor = false; + bool autoColor = true; + bool dontAppend = false; - /* 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); - } - } + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; - /* 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; - } + 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; + } - const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); - if (!var) { - ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); - if (v.type) { - return v.type; + 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; } - ecs_parser_error(name, expr, ptr - expr, - "unresolved variable '%s'", token); - return 0; - } - return var->value.type; - } + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + isVar = false; + } - /* Boolean */ - if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { - if (!isalpha(ptr[4]) && ptr[4] != '_') { - return ecs_id(ecs_bool_t); + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); + isVar = true; + } } - } - if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { - if (!isalpha(ptr[5]) && ptr[5] != '_') { - return ecs_id(ecs_bool_t); + + 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; + } } - } - /* Entity identifier */ - if (isalpha(ptr[0])) { - if (!input_type) { /* Identifier could also be enum/bitmask constant */ - return ecs_id(ecs_entity_t); + 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 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 (!dontAppend) { + ecs_strbuf_appendstrn(buf, ptr, 1); } - if (ptr[0] == '[') { - ecs_parser_error(name, expr, ptr - expr, - "unknown type for collection literal"); - return 0; + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } } - ecs_parser_error(name, expr, ptr - expr, "invalid expression"); + prev = ch; } - return input_type; + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } } -/* 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) +void ecs_printv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) { - 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); - default: ecs_abort(ECS_INTERNAL_ERROR, NULL); + (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, ""); } - 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) +void ecs_print_( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) { - 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; + va_list args; + va_start(args, fmt); + ecs_printv_(level, file, line, fmt, args); + va_end(args); } -static -bool flecs_oper_valid_for_type( - ecs_entity_t type, - ecs_expr_oper_t op) +void ecs_logv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) { - 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)); - default: - return false; + if (level > ecs_os_api.log_level_) { + return; } + + ecs_printv_(level, file, line, fmt, args); } -/** Promote type to most expressive (f64 > i64 > u64) */ -static -ecs_entity_t flecs_promote_type( - ecs_entity_t type, - ecs_entity_t promote_to) +void ecs_log_( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) { - 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 (level > ecs_os_api.log_level_) { + return; } - if (promote_to == ecs_id(ecs_f64_t)) { - return promote_to; + + 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_ ++; } - return ecs_id(ecs_i64_t); } -static -int flecs_oper_precedence( - ecs_expr_oper_t left, - ecs_expr_oper_t right) +void ecs_log_pop_( + int32_t level) { - return (left > right) - (left < right); + if (level <= ecs_os_api.log_level_) { + ecs_os_api.log_indent_ --; + ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); + } } -static -void flecs_value_cast( - ecs_world_t *world, - ecs_value_stack_t *stack, - ecs_value_t *value, - ecs_entity_t type) +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) { - if (value->type == type) { - return; + 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); - ecs_value_t result; - result.type = type; - result.ptr = flecs_expr_value_new(stack, type); + if (ecs_os_api.log_level_ >= -2) { + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; - if (value->ptr) { - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); - ecs_meta_set_value(&cur, value); - } + ecs_strbuf_vappend(&msg_buf, fmt, args); - *value = result; -} + if (expr) { + ecs_strbuf_appendch(&msg_buf, '\n'); -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; - default: - return false; + /* 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); } } -static -ecs_entity_t flecs_binary_expr_type( - ecs_world_t *world, +void ecs_parser_error_( 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) + const char *expr, + int64_t column, + const char *fmt, + ...) { - 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; - default: - break; - } - - /* 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; + 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); } +} - 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; +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; +} - if (flecs_expr_op_is_equality(op)) { - ecs_parser_error(name, expr, ptr - expr, - "mismatching types in equality expression"); - return 0; +bool ecs_assert_( + bool condition, + int32_t err, + const char *cond_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (!condition) { + 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; } - 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; - } + return condition; +} - operand_type = flecs_promote_type(ltype, rtype); +void ecs_deprecated_( + const char *file, + int32_t line, + const char *msg) +{ + ecs_err_(file, line, "%s", msg); +} -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); +bool ecs_should_log(int32_t level) { +# if !defined(FLECS_LOG_3) + if (level == 3) { + return false; } - - if (!result_type) { - result_type = operand_type; +# endif +# if !defined(FLECS_LOG_2) + if (level == 2) { + return false; + } +# endif +# if !defined(FLECS_LOG_1) + if (level == 1) { + return false; } +# endif - *operand_type_out = operand_type; - return result_type; + return level <= ecs_os_api.log_level_; } -/* 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_ERR_STR(code) case code: return &(#code[4]) -#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_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) +const char* ecs_strerror( + int32_t error_code) { - /* Find expression type */ - ecs_entity_t operand_type, type = flecs_binary_expr_type( - world, name, expr, ptr, lvalue, rvalue, op, &operand_type); - if (!type) { - return -1; - } - - if (!flecs_oper_valid_for_type(type, op)) { - ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); - return -1; + 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); } - flecs_value_cast(world, stack, lvalue, operand_type); - flecs_value_cast(world, stack, rvalue, operand_type); + return "unknown error code"; +} - 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); - } +#else - 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_OP(lvalue, rvalue, storage, ==); - break; - case EcsCondNeq: - ECS_BINARY_COND_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; - default: - ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); - return -1; - } +/* Empty bodies for when logging is disabled */ - 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); - } - } +void ecs_log_( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)level; + (void)file; + (void)line; + (void)fmt; +} - return 0; +void ecs_parser_error_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; } -static -const char* flecs_binary_expr_parse( - ecs_world_t *world, - ecs_value_stack_t *stack, +void ecs_parser_errorv_( 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) + const char *expr, + int64_t column, + const char *fmt, + va_list args) { - 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; - } + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; +} - ptr = ecs_parse_ws_eol(ptr); +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; +} - ecs_value_t rvalue = {0}; - const char *rptr = flecs_parse_expr(world, stack, ptr, &rvalue, op, desc); - if (!rptr) { - return NULL; - } +bool ecs_assert_( + bool condition, + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)condition; + (void)error_code; + (void)condition_str; + (void)file; + (void)line; + (void)fmt; + return true; +} - if (flecs_binary_expr_do(world, stack, name, expr, ptr, - lvalue, &rvalue, result, op)) - { - return NULL; - } +#endif - ptr = rptr; +int ecs_log_get_level(void) { + return ecs_os_api.log_level_; +} - 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; - } - } +int ecs_log_set_level( + int level) +{ + int prev = level; + ecs_os_api.log_level_ = level; + return prev; +} - break; - } while (true); +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; +} - return ptr; +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; } -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) +bool ecs_log_enable_timedelta( + bool enabled) { - 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; + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); + return prev; +} - ptr = ecs_parse_ws_eol(ptr); +int ecs_log_last_error(void) +{ + int result = ecs_os_api.log_last_error_; + ecs_os_api.log_last_error_ = 0; + return result; +} - /* 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); - } +/** + * @file addons/meta_c.c + * @brief C utilities for meta addon. + */ - /* 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; - } +#ifdef FLECS_META_C - cur = ecs_meta_cursor(world, result.type, result.ptr); - if (!cur.valid) { - return NULL; - } +#include - cur.lookup_action = desc ? desc->lookup_action : NULL; - cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; - } +#define ECS_META_IDENTIFIER_LENGTH (256) - /* 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; +#define ecs_meta_error(ctx, ptr, ...)\ + ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); - 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; - } +typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; - /* 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; +typedef struct meta_parse_ctx_t { + const char *name; + const char *desc; +} meta_parse_ctx_t; - } else if (!ecs_os_strcmp(token, "{")) { - /* Parse nested value scope */ - ecs_entity_t scope_type = ecs_meta_get_type(&cur); +typedef struct meta_type_t { + ecs_meta_token_t type; + ecs_meta_token_t params; + bool is_const; + bool is_ptr; +} meta_type_t; - depth ++; /* Keep track of depth so we know when parsing is done */ - if (ecs_meta_push(&cur) != 0) { - goto error; - } +typedef struct meta_member_t { + meta_type_t type; + ecs_meta_token_t name; + int64_t count; + bool is_partial; +} meta_member_t; - 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; - } - } +typedef struct meta_constant_t { + ecs_meta_token_t name; + int64_t value; + bool is_value_set; +} meta_constant_t; - else if (!ecs_os_strcmp(token, "}")) { - depth --; +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; - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; - } +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; - if (ecs_meta_pop(&cur) != 0) { + 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; } } - else if (!ecs_os_strcmp(token, "[")) { - /* Open collection value scope */ - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + ptr ++; - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '{'"); - return NULL; - } + if (!sp) { + break; } + } - else if (!ecs_os_strcmp(token, "]")) { - depth --; + return ptr; +error: + return NULL; +} - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); - 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; + } - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } + *value_out = strtol(token, NULL, 0); - 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; - } + return ecs_parse_ws_eol(ptr); +error: + return NULL; +} - else if (!ecs_os_strcmp(token, ",")) { - /* Move to next field */ - if (ecs_meta_next(&cur) != 0) { - goto error; - } - } +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); - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; - } + char *bptr = buff, ch; - is_lvalue = true; - } + if (params) { + params[0] = '\0'; + } - else if (token[0] == '\"') { - /* Regular string */ - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; - } + /* Ignore whitespaces */ + ptr = ecs_parse_ws_eol(ptr); + ch = *ptr; - is_lvalue = true; - } + if (!isalpha(ch) && (ch != '_')) { + ecs_meta_error(ctx, ptr, "invalid identifier (starts with '%c')", ch); + goto error; + } - else if (!ecs_os_strcmp(token, "`")) { - /* Multiline string */ - if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { + 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; } - 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) { - 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; + const char *end = skip_scope(ptr, ctx); + ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); + params[end - ptr] = '\0'; + ptr = end; } 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 (ecs_meta_set_string(&cur, token) != 0) { - goto error; - } - } - - is_lvalue = true; + *bptr = ch; + bptr ++; + ptr ++; } + } - /* 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; - } + *bptr = '\0'; - flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, - &rvalue, out, EcsMul); - } - unary_op = 0; - } + if (!ch) { + ecs_meta_error(ctx, ptr, "unexpected end of token"); + goto error; + } - 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; - } + return ptr; +error: + return NULL; +} - /* Do the binary expression */ - ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, - &lvalue, op_result, left_op, desc); - if (!ptr) { - 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); - if (!depth) { - /* Reached the end of the root scope */ - break; + /* 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); } - if (!value->ptr) { - value->type = result.type; - value->ptr = flecs_expr_value_new(stack, result.type); - } + /* Is this the end of the type definition? */ + if (!*ptr) { + ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); + goto error; + } - if (value->ptr != result.ptr) { - cur = ecs_meta_cursor(world, value->type, value->ptr); - ecs_meta_set_value(&cur, &result); + /* 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; } - 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, +static +const char* meta_parse_constant( 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); + meta_constant_t *token, + meta_parse_ctx_t *ctx) +{ + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } - /* Parse expression */ - bool storage_provided = value->ptr != NULL; - ptr = flecs_parse_expr(world, &stack, ptr, value, EcsExprOperUnknown, desc); + token->is_value_set = false; - /* 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); + /* Parse token, constant identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + return NULL; } - flecs_stack_restore_cursor(stack.stack, &stack.cursor); - return ptr; -} + ptr = ecs_parse_ws_eol(ptr); + if (!ptr) { + return NULL; + } -#endif + /* Explicit value assignment */ + if (*ptr == '=') { + int64_t value = 0; + ptr = parse_c_digit(ptr + 1, &value); + token->value = value; + token->is_value_set = true; + } -/** - * @file addons/stats.c - * @brief Stats addon. - */ + /* 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; +} -#ifdef FLECS_SYSTEM -#endif +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; -#ifdef FLECS_PIPELINE -#endif + ptr = ecs_parse_ws_eol(ptr); -#ifdef FLECS_STATS + /* Parse token, expect type identifier or ECS_PROPERTY */ + ptr = parse_c_identifier(ptr, token->type, token->params, ctx); + if (!ptr) { + goto error; + } -#define ECS_GAUGE_RECORD(m, t, value)\ - flecs_gauge_record(m, t, (ecs_float_t)(value)) + if (!strcmp(token->type, "ECS_PRIVATE")) { + /* Members from this point are not stored in metadata */ + ptr += ecs_os_strlen(ptr); + goto done; + } -#define ECS_COUNTER_RECORD(m, t, value)\ - flecs_counter_record(m, t, (double)(value)) + /* If token is const, set const flag and continue parsing type */ + if (!strcmp(token->type, "const")) { + token->is_const = true; -#define ECS_METRIC_FIRST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) + /* Parse type after const */ + ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); + } -#define ECS_METRIC_LAST(stats)\ - ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) + /* Check if type is a pointer */ + ptr = ecs_parse_ws_eol(ptr); + if (*ptr == '*') { + token->is_ptr = true; + ptr ++; + } -static -int32_t t_next( - int32_t t) -{ - return (t + 1) % ECS_STAT_WINDOW; +done: + return ptr; +error: + return NULL; } static -int32_t t_prev( - int32_t t) +const char* meta_parse_member( + const char *ptr, + meta_member_t *token, + meta_parse_ctx_t *ctx) { - return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; -} + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } -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; -} + token->count = 1; + token->is_partial = false; -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 */ + /* Parse member type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + token->is_partial = true; + goto error; } - 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); -} + if (!ptr[0]) { + return ptr; + } -static -void flecs_gauge_print( - const char *name, - int32_t t, - const ecs_metric_t *m) -{ - flecs_metric_print(name, m->gauge.avg[t]); -} + /* Next token is the identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + goto error; + } -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]); -} + /* Skip whitespace between member and [ or ; */ + ptr = ecs_parse_ws_eol(ptr); -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); + /* 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); + } + } - bool min_set = false; - dst->gauge.avg[t_dst] = 0; - dst->gauge.min[t_dst] = 0; - dst->gauge.max[t_dst] = 0; + 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; - ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; + } else if (array_end - array_start == 0) { + ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); + goto error; + } - 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; + token->count = atoi(array_start + 1); - 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]; + 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'; } } - dst->counter.value[t_dst] = src->counter.value[t_src]; + /* Expect a ; */ + if (*ptr != ';') { + ecs_meta_error(ctx, ptr, "missing ; after member declaration"); + goto error; + } + return ptr + 1; error: - return; + return NULL; } -void ecs_metric_reduce_last( - ecs_metric_t *m, - int32_t prev, - int32_t count) +static +int meta_parse_desc( + const char *ptr, + meta_params_t *token, + meta_parse_ctx_t *ctx) { - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t t = t_next(prev); + token->is_key_value = false; + token->is_fixed_size = false; - if (m->gauge.min[t] < m->gauge.min[prev]) { - m->gauge.min[prev] = m->gauge.min[t]; + ptr = ecs_parse_ws_eol(ptr); + if (*ptr != '(' && *ptr != '<') { + ecs_meta_error(ctx, ptr, + "expected '(' at start of collection definition"); + goto error; } - if (m->gauge.max[t] > m->gauge.max[prev]) { - m->gauge.max[prev] = m->gauge.max[t]; + ptr ++; + + /* Parse type identifier */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + goto error; } - 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]; + ptr = ecs_parse_ws_eol(ptr); - cur *= ((fcount - 1) / fcount); - next *= 1 / fcount; + /* 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; + } - m->gauge.avg[prev] = cur + next; - m->counter.value[prev] = m->counter.value[t]; + token->count = value; + token->is_fixed_size = true; + } else { + token->key_type = token->type; -error: - return; -} + /* Parse element type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + ptr = ecs_parse_ws_eol(ptr); -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); + token->is_key_value = true; + } + } - 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]; + if (*ptr != ')' && *ptr != '>') { + ecs_meta_error(ctx, ptr, + "expected ')' at end of collection definition"); + goto error; + } + return 0; error: - return; + return -1; } 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); - } -} +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 -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) +ecs_entity_t meta_lookup_array( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) { - 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); + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; - /* 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]; + 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; } -} -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); + if (!params.count) { + ecs_meta_error(ctx, params_decl, "invalid array size"); + goto error; } -} -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]; + 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); } -} -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); + if (!e) { + e = ecs_new_id(world); + } - world = ecs_get_world(world); + ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); - int32_t t = s->t = t_next(s->t); + return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); +error: + return 0; +} - 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); +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 + }; - 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 (delta_world_time != 0 && delta_frame_count != 0) { - 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); + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; } - 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)); + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for vector"); + goto error; + } - ECS_GAUGE_RECORD(&s->ids.count, t, world->info.id_count); - ECS_GAUGE_RECORD(&s->ids.tag_count, t, world->info.tag_id_count); - ECS_GAUGE_RECORD(&s->ids.component_count, t, world->info.component_id_count); - ECS_GAUGE_RECORD(&s->ids.pair_count, t, world->info.pair_id_count); - ECS_GAUGE_RECORD(&s->ids.wildcard_count, t, world->info.wildcard_id_count); - ECS_GAUGE_RECORD(&s->ids.type_count, t, ecs_sparse_count(&world->type_info)); - ECS_COUNTER_RECORD(&s->ids.create_count, t, world->info.id_create_total); - ECS_COUNTER_RECORD(&s->ids.delete_count, t, world->info.id_delete_total); + ecs_entity_t element_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); - 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)); + if (!e) { + e = ecs_new_id(world); } - 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_GAUGE_RECORD(&s->tables.tag_only_count, t, world->info.tag_table_count); - ECS_GAUGE_RECORD(&s->tables.trivial_only_count, t, world->info.trivial_table_count); - ECS_GAUGE_RECORD(&s->tables.storage_count, t, world->info.table_storage_count); - ECS_GAUGE_RECORD(&s->tables.record_count, t, world->info.table_record_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); + return ecs_set(world, e, EcsVector, { element_type }); +error: + return 0; +} - 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); +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; - 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); + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; - 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); + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } -#ifdef FLECS_REST - ECS_COUNTER_RECORD(&s->rest.request_count, t, ecs_rest_request_count); - ECS_COUNTER_RECORD(&s->rest.entity_count, t, ecs_rest_entity_count); - ECS_COUNTER_RECORD(&s->rest.entity_error_count, t, ecs_rest_entity_error_count); - ECS_COUNTER_RECORD(&s->rest.query_count, t, ecs_rest_query_count); - ECS_COUNTER_RECORD(&s->rest.query_error_count, t, ecs_rest_query_error_count); - ECS_COUNTER_RECORD(&s->rest.query_name_count, t, ecs_rest_query_name_count); - ECS_COUNTER_RECORD(&s->rest.query_name_error_count, t, ecs_rest_query_name_error_count); - ECS_COUNTER_RECORD(&s->rest.query_name_from_cache_count, t, ecs_rest_query_name_from_cache_count); - ECS_COUNTER_RECORD(&s->rest.enable_count, t, ecs_rest_enable_count); - ECS_COUNTER_RECORD(&s->rest.enable_error_count, t, ecs_rest_enable_error_count); - ECS_COUNTER_RECORD(&s->rest.world_stats_count, t, ecs_rest_world_stats_count); - ECS_COUNTER_RECORD(&s->rest.pipeline_stats_count, t, ecs_rest_pipeline_stats_count); - ECS_COUNTER_RECORD(&s->rest.stats_error_count, t, ecs_rest_stats_error_count); -#endif + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for bitmask"); + goto error; + } -#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 + if (params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, + "unexpected size for bitmask"); + goto error; + } -error: - return; -} + ecs_entity_t bitmask_type = meta_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); + ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); -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); -} +#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 -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); + return bitmask_type; +error: + return 0; } -void ecs_world_stats_repeat_last( - ecs_world_stats_t *stats) +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) { - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); -} + 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); -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)); -} + const char *typename = token->type; + ecs_entity_t type = 0; -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; + /* Parse vector type */ + if (!token->is_ptr) { + if (!ecs_os_strcmp(typename, "ecs_array")) { + type = meta_lookup_array(world, 0, token->params, ctx); - int32_t t = s->t = t_next(s->t); + } else if (!ecs_os_strcmp(typename, "ecs_vector") || + !ecs_os_strcmp(typename, "flecs::vector")) + { + type = meta_lookup_vector(world, 0, token->params, ctx); - 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); - } + } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { + type = meta_lookup_bitmask(world, 0, token->params, ctx); -error: - return; -} + } else if (!ecs_os_strcmp(typename, "flecs::byte")) { + type = ecs_id(ecs_byte_t); -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); -} + } else if (!ecs_os_strcmp(typename, "char")) { + type = ecs_id(ecs_char_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); -} + } else if (!ecs_os_strcmp(typename, "bool") || + !ecs_os_strcmp(typename, "_Bool")) + { + type = ecs_id(ecs_bool_t); -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))); -} + } 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); -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)); -} + } 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); -#ifdef FLECS_SYSTEM + } 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); -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); + } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { + type = ecs_id(ecs_entity_t); - world = ecs_get_world(world); + } 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"; + } - const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t); - if (!ptr) { - return false; + type = ecs_lookup_symbol(world, typename, true, true); } - ecs_query_stats_get(world, ptr->query, &s->query); - int32_t t = s->query.t; + if (count != 1) { + ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); - ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); - ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); - ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsEmpty)); - ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); + } - s->task = !(ptr->query->filter.flags & EcsFilterMatchThis); + if (!type) { + ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); + goto error; + } - return true; + return type; error: - return false; + return 0; } -void ecs_system_stats_reduce( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) +static +int meta_parse_struct( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) { - 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); -} + const char *ptr = desc; + const char *name = ecs_get_name(world, 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); -} + meta_member_t token; + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; -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 + ecs_entity_t old_scope = ecs_set_scope(world, t); -#ifdef FLECS_PIPELINE + while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { + ecs_entity_t m = ecs_entity(world, { + .name = token.name + }); -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); + ecs_entity_t type = meta_lookup( + world, &token.type, ptr, 1, &ctx); + if (!type) { + goto error; + } - const ecs_world_t *world = ecs_get_world(stage); - const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); - if (!pqc) { - return false; + ecs_set(world, m, EcsMember, { + .type = type, + .count = (ecs_size_t)token.count + }); } - ecs_pipeline_state_t *pq = pqc->state; - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t sys_count = 0, active_sys_count = 0; + ecs_set_scope(world, old_scope); - /* 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; - } + return 0; +error: + return -1; +} - /* Count total number of systems in pipeline */ - it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - sys_count += it.count; - } +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); - /* 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); + 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; - if (!sys_count) { - return false; - } + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; - if (ecs_map_is_init(&s->system_stats) && !sys_count) { - ecs_map_fini(&s->system_stats); - } - ecs_map_init_if(&s->system_stats, NULL); + meta_constant_t token; + int64_t last_value = 0; - /* Make sure vector is large enough to store all systems & sync points */ - 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); + ecs_entity_t old_scope = ecs_set_scope(world, 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; - } + 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; + } - 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 */ - } - } + 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); } - - 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); } - } - /* 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)) { - int 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); + 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); } - } - s->t = t_next(s->t); + ecs_entity_t c = ecs_entity(world, { + .name = token.name + }); - return true; -error: - return false; -} + 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}); + } -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); + last_value ++; } - ecs_map_fini(&stats->system_stats); - ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_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); - ecs_map_init_if(&dst->system_stats, NULL); + ecs_set_scope(world, old_scope); - 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); + return 0; +error: + return -1; } -void ecs_pipeline_stats_reduce_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src, - int32_t count) +static +int meta_parse_enum( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) { - 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); + ecs_add(world, t, EcsEnum); + return meta_parse_constants(world, t, desc, false); } -void ecs_pipeline_stats_repeat_last( - ecs_pipeline_stats_t *stats) +static +int meta_parse_bitmask( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) { - 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); + ecs_add(world, t, EcsBitmask); + return meta_parse_constants(world, t, desc, true); } -void ecs_pipeline_stats_copy_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc) { - 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); + 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"); } -} - -#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("id count", t, &s->ids.count); - flecs_gauge_print("tag id count", t, &s->ids.tag_count); - flecs_gauge_print("component id count", t, &s->ids.component_count); - flecs_gauge_print("pair id count", t, &s->ids.pair_count); - flecs_gauge_print("wildcard id count", t, &s->ids.wildcard_count); - flecs_gauge_print("type count", t, &s->ids.type_count); - flecs_counter_print("id create count", t, &s->ids.create_count); - flecs_counter_print("id delete count", t, &s->ids.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_gauge_print("tag table count", t, &s->tables.tag_only_count); - flecs_gauge_print("trivial table count", t, &s->tables.trivial_only_count); - flecs_gauge_print("table storage count", t, &s->tables.storage_count); - flecs_gauge_print("table cache record count", t, &s->tables.record_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(""); - + return 0; error: - return; + return -1; } #endif /** - * @file addons/units.c - * @brief Units addon. + * @file addons/metrics.c + * @brief Metrics addon. */ -#ifdef FLECS_UNITS +#ifdef FLECS_METRICS -ECS_DECLARE(EcsUnitPrefixes); +/* 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); -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); +/* 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); -ECS_DECLARE(EcsKibi); -ECS_DECLARE(EcsMebi); -ECS_DECLARE(EcsGibi); -ECS_DECLARE(EcsTebi); -ECS_DECLARE(EcsPebi); -ECS_DECLARE(EcsExbi); -ECS_DECLARE(EcsZebi); -ECS_DECLARE(EcsYobi); +/** Context for metric */ +typedef struct { + ecs_entity_t metric; /**< Metric entity */ + ecs_entity_t kind; /**< Metric kind (gauge, counter) */ +} ecs_metric_ctx_t; -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); +/** 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; -ECS_DECLARE(EcsTime); - ECS_DECLARE(EcsDate); +/** 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; -ECS_DECLARE(EcsMass); - ECS_DECLARE(EcsGrams); - ECS_DECLARE(EcsKiloGrams); +/** 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; -ECS_DECLARE(EcsElectricCurrent); - ECS_DECLARE(EcsAmpere); +/** 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; -ECS_DECLARE(EcsAmount); - ECS_DECLARE(EcsMole); +/** Stores context shared for all instances of member metric */ +typedef struct { + ecs_member_metric_ctx_t *ctx; +} EcsMetricMember; -ECS_DECLARE(EcsLuminousIntensity); - ECS_DECLARE(EcsCandela); +/** Stores context shared for all instances of id metric */ +typedef struct { + ecs_id_metric_ctx_t *ctx; +} EcsMetricId; -ECS_DECLARE(EcsForce); - ECS_DECLARE(EcsNewton); +/** Stores context shared for all instances of oneof metric */ +typedef struct { + ecs_oneof_metric_ctx_t *ctx; +} EcsMetricOneOf; -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); +/** Stores context shared for all instances of id counter metric */ +typedef struct { + ecs_id_t id; +} EcsMetricCountIds; -ECS_DECLARE(EcsPressure); - ECS_DECLARE(EcsPascal); - ECS_DECLARE(EcsBar); +/** Stores context shared for all instances of target counter metric */ +typedef struct { + ecs_count_targets_metric_ctx_t *ctx; +} EcsMetricCountTargets; -ECS_DECLARE(EcsSpeed); - ECS_DECLARE(EcsMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerHour); - ECS_DECLARE(EcsMilesPerHour); +/** Instance of member metric */ +typedef struct { + ecs_ref_t ref; + ecs_member_metric_ctx_t *ctx; +} EcsMetricMemberInstance; -ECS_DECLARE(EcsAcceleration); +/** Instance of id metric */ +typedef struct { + ecs_record_t *r; + ecs_id_metric_ctx_t *ctx; +} EcsMetricIdInstance; -ECS_DECLARE(EcsTemperature); - ECS_DECLARE(EcsKelvin); - ECS_DECLARE(EcsCelsius); - ECS_DECLARE(EcsFahrenheit); +/** Instance of oneof metric */ +typedef struct { + ecs_record_t *r; + ecs_oneof_metric_ctx_t *ctx; +} EcsMetricOneOfInstance; -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); +/** Component lifecycle */ -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); +static ECS_DTOR(EcsMetricMember, ptr, { + ecs_os_free(ptr->ctx); +}) -ECS_DECLARE(EcsPercentage); +static ECS_MOVE(EcsMetricMember, dst, src, { + *dst = *src; + src->ctx = NULL; +}) -ECS_DECLARE(EcsAngle); - ECS_DECLARE(EcsRadians); - ECS_DECLARE(EcsDegrees); +static ECS_DTOR(EcsMetricId, ptr, { + ecs_os_free(ptr->ctx); +}) -ECS_DECLARE(EcsBel); -ECS_DECLARE(EcsDeciBel); +static ECS_MOVE(EcsMetricId, dst, src, { + *dst = *src; + src->ctx = NULL; +}) -ECS_DECLARE(EcsFrequency); - ECS_DECLARE(EcsHertz); - ECS_DECLARE(EcsKiloHertz); - ECS_DECLARE(EcsMegaHertz); - ECS_DECLARE(EcsGigaHertz); +static ECS_DTOR(EcsMetricOneOf, ptr, { + if (ptr->ctx) { + ecs_map_fini(&ptr->ctx->target_offset); + ecs_os_free(ptr->ctx); + } +}) -ECS_DECLARE(EcsUri); - ECS_DECLARE(EcsUriHyperlink); - ECS_DECLARE(EcsUriImage); - ECS_DECLARE(EcsUriFile); +static ECS_MOVE(EcsMetricOneOf, dst, src, { + *dst = *src; + src->ctx = NULL; +}) -void FlecsUnitsImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsUnits); +static ECS_DTOR(EcsMetricCountTargets, ptr, { + if (ptr->ctx) { + ecs_map_fini(&ptr->ctx->targets); + ecs_os_free(ptr->ctx); + } +}) - ecs_set_name_prefix(world, "Ecs"); +static ECS_MOVE(EcsMetricCountTargets, dst, src, { + *dst = *src; + src->ctx = NULL; +}) - EcsUnitPrefixes = ecs_entity(world, { - .name = "prefixes", - .add = { EcsModule } - }); +/** 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); - /* Initialize unit prefixes */ + 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); - ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); + 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); + } +} - 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 } - }); +/** 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; - 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 } - }); + 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); - ecs_set_scope(world, prev_scope); + 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); + } +} - /* Duration units */ +/** 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; + } - EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Duration" }); - prev_scope = ecs_set_scope(world, EcsDuration); + ecs_world_t *world = it->world; + ecs_oneof_metric_ctx_t *ctx = it->ctx; - 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 - }); + 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); + } +} - 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 - }); +/** 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); - 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 - }); + 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 - 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 - }); +/** 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); - 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 - }); + 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]); + } + } +} - 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 - }); +/** 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; - 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); + 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]); + } + } +} - /* Time units */ +static void UpdateGaugeMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, false); +} - EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Time" }); - prev_scope = ecs_set_scope(world, EcsTime); +static void UpdateCounterMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, false); +} - 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); +static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, true); +} - /* Mass units */ +/** 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; - 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); + 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; + } - /* Electric current units */ + 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]); + } + } +} - 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); +static void UpdateGaugeIdInstance(ecs_iter_t *it) { + UpdateIdInstance(it, false); +} - /* Amount of substance units */ +static void UpdateCounterIdInstance(ecs_iter_t *it) { + UpdateIdInstance(it, true); +} - 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); +/** 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; - /* Luminous intensity units */ + 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; - 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); + double *value = ECS_ELEM(m, ctx->size, i); + if (!counter) { + ecs_os_memset(value, 0, ctx->size); + } - /* Force units */ + if (!mtable) { + ecs_delete(it->world, it->entities[i]); + continue; + } - 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); + 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; + } - /* Length units */ + 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; + } - 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 - }); + value = ECS_OFFSET(value, *offset); - 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 - }); + if (!counter) { + *value = 1.0; + } else { + *value += 1.0 * (double)dt; + } + } +} - 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 - }); +static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { + UpdateOneOfInstance(it, false); +} - 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 - }); +static void UpdateCounterOneOfInstance(ecs_iter_t *it) { + UpdateOneOfInstance(it, true); +} - 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 - }); +static void UpdateCountTargets(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 1); - 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 - }); + 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); + } - 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 - }); + EcsMetricSource *source = ecs_get_mut( + world, mi[0], EcsMetricSource); + source->entity = tgt; + } - 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); + EcsMetricValue *value = ecs_get_mut(world, mi[0], EcsMetricValue); + value->value += (double)ecs_count_id(world, cur->id) * + (double)it->delta_system_time; + } + } +} - /* Pressure units */ +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); - 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); + 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; + } +} - /* Speed units */ +/** 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; + uintptr_t offset = 0; - 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 */ + 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; + } - 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 - }); + 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; + } - /* Temperature units */ + ecs_meta_cursor_t cur = ecs_meta_cursor(world, desc->id, 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; + } - 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); + type = 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; + } - /* Data units */ + 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; + } + + member = desc->member; + member_type = m->type; + offset = flecs_ito(uintptr_t, m->offset); + } - EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Data" }); - prev_scope = ecs_set_scope(world, EcsData); + 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; + } - 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 - }); + 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; + } - 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 - }); + 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; + } - 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 - }); + 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); - 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 - }); + ecs_observer(world, { + .entity = metric, + .events = { EcsOnAdd }, + .filter.terms[0] = { + .id = type, + .src.flags = EcsSelf, + .inout = EcsInOutNone + }, + .callback = flecs_metrics_on_member_metric, + .yield_existing = true, + .ctx = ctx + }); - 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 - }); + ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); - 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 - }); + return 0; +error: + return -1; +} - 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 - }); +/** 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); - 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 - }); + 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 + }); - 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 - }); + ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); - 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 - }); + return 0; +error: + return -1; +} - 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 - }); +/** 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); - ecs_set_scope(world, prev_scope); + /* 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; + } - /* DataRate units */ + char *to_snake_case = flecs_to_snake_case(name); - EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "DataRate" }); - prev_scope = ecs_set_scope(world, EcsDataRate); + ecs_entity_t mbr = ecs_entity(world, { + .name = to_snake_case, + .add = { ecs_childof(metric) } + }); - 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 - }); + ecs_os_free(to_snake_case); - 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 + ecs_set(world, mbr, EcsMember, { + .type = ecs_id(ecs_f64_t), + .unit = EcsSeconds }); - 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 - }); + /* 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; - 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 - }); + offset += sizeof(double); + } + } - 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 - }); + ctx->size = flecs_uto(ecs_size_t, offset); - 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 - }); + 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 + }); - 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 - }); + ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); - 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 - }); + return 0; +error: + return -1; +} - ecs_set_scope(world, prev_scope); +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); - /* Percentage */ + ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); - 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 - }); + return 0; +error: + return -1; +} - /* Angles */ +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; +} - 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 - }); +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); - 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); + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new_id(world); + } - /* DeciBel */ + ecs_entity_t kind = desc->kind; + if (!kind) { + ecs_err("missing metric kind"); + goto error; + } - 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 - }); + if (kind != EcsGauge && + kind != EcsCounter && + kind != EcsCounterId && + kind != EcsCounterIncrement) + { + ecs_err("invalid metric kind %s", ecs_get_fullpath(world, kind)); + goto error; + } - EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Frequency" }); - prev_scope = ecs_set_scope(world, EcsFrequency); + if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { + ecs_err("CounterIncrement can only be used in combination with member"); + goto error; + } - 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 - }); + if (kind == EcsCounterId && (desc->member || desc->dotmember)) { + ecs_err("CounterId cannot be used in combination with member"); + goto error; + } - 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 - }); + 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 + } - 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 - }); + if (desc->member || desc->dotmember) { + if (desc->id && desc->member) { + ecs_err("cannot specify both member and id for metric"); + goto error; + } + 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; + } - 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); + 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; + } - EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ - .name = "Uri" }); - prev_scope = ecs_set_scope(world, EcsUri); + 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; + } - 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 - }); + return result; +error: + if (result && result != desc->entity) { + ecs_delete(world, result); + } + return 0; +} - 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 - }); +void FlecsMetricsImport(ecs_world_t *world) { + ECS_MODULE_DEFINE(world, FlecsMetrics); - 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); + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsMeta); + ECS_IMPORT(world, FlecsUnits); - /* Documentation */ -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); + 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_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_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_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_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); + ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); + ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); - ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); + ecs_struct(world, { + .entity = ecs_id(EcsMetricValue), + .members = { + { .name = "value", .type = ecs_id(ecs_f64_t) } + } + }); - ecs_doc_set_brief(world, EcsElectricCurrent, - "Units of electrical current (e.g. \"2 ampere\")"); + ecs_struct(world, { + .entity = ecs_id(EcsMetricSource), + .members = { + { .name = "entity", .type = ecs_id(ecs_entity_t) } + } + }); - ecs_doc_set_brief(world, EcsAmount, - "Units of amount of substance (e.g. \"2 mole\")"); + ecs_set_hooks(world, EcsMetricMember, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricMember), + .move = ecs_move(EcsMetricMember) + }); - ecs_doc_set_brief(world, EcsLuminousIntensity, - "Units of luminous intensity (e.g. \"1 candela\")"); + ecs_set_hooks(world, EcsMetricId, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricId), + .move = ecs_move(EcsMetricId) + }); - ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); + ecs_set_hooks(world, EcsMetricOneOf, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricOneOf), + .move = ecs_move(EcsMetricOneOf) + }); - ecs_doc_set_brief(world, EcsLength, - "Units of length (e.g. \"5 meters\", \"20 miles\")"); + ecs_set_hooks(world, EcsMetricCountTargets, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsMetricCountTargets), + .move = ecs_move(EcsMetricCountTargets) + }); - ecs_doc_set_brief(world, EcsPressure, - "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); + ecs_add_id(world, EcsMetric, EcsOneOf); - ecs_doc_set_brief(world, EcsSpeed, - "Units of movement (e.g. \"5 meters/second\")"); +#ifdef FLECS_DOC + ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, EcsMetricSource); +#endif - ecs_doc_set_brief(world, EcsAcceleration, - "Unit of speed increase (e.g. \"5 meters/second/second\")"); + ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, + [in] Source); - ecs_doc_set_brief(world, EcsTemperature, - "Units of temperature (e.g. \"5 degrees Celsius\")"); + ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, Gauge)); - ecs_doc_set_brief(world, EcsData, - "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); + ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, Counter)); - ecs_doc_set_brief(world, EcsDataRate, - "Units of data transmission (e.g. \"100 megabits/second\")"); + ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, CounterIncrement)); - ecs_doc_set_brief(world, EcsAngle, - "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); + ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, + [out] Value, + [in] IdInstance, + [none] (Metric, Gauge)); - ecs_doc_set_brief(world, EcsFrequency, - "The number of occurrences of a repeating event per unit of time."); + ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, + [inout] Value, + [in] IdInstance, + [none] (Metric, Counter)); - ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); -#endif + 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/snapshot.c - * @brief Snapshot addon. + * @file addons/module.c + * @brief Module addon. */ -#ifdef FLECS_SNAPSHOT +#ifdef FLECS_MODULE +#include -/* World snapshot */ -struct ecs_snapshot_t { - ecs_world_t *world; - ecs_entity_index_t entity_index; - ecs_vec_t tables; - uint64_t last_id; -}; +char* ecs_module_path_from_c( + const char *c_name) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + const char *ptr; + char ch; -/** 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; + 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); + } + } -static -ecs_data_t* flecs_duplicate_data( + ecs_strbuf_appendstrn(&str, &ch, 1); + } + + return ecs_strbuf_get(&str); +} + +ecs_entity_t ecs_import( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *main_data) + ecs_module_action_t module, + const char *module_name) { - if (!ecs_table_count(table)) { - return NULL; - } + ecs_check(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); - ecs_data_t *result = ecs_os_calloc_t(ecs_data_t); - int32_t i, column_count = table->storage_count; - result->columns = flecs_wdup_n(world, ecs_vec_t, column_count, - main_data->columns); + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->info.name_prefix; - /* Copy entities and records */ - ecs_allocator_t *a = &world->allocator; - result->entities = ecs_vec_copy_t(a, &main_data->entities, ecs_entity_t); - result->records = ecs_vec_copy_t(a, &main_data->records, ecs_record_t*); + 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(); - /* Copy each column */ - for (i = 0; i < column_count; i ++) { - ecs_vec_t *column = &result->columns[i]; - ecs_type_info_t *ti = table->type_info[i]; - int32_t size = ti->size; - ecs_copy_t copy = ti->hooks.copy; - if (copy) { - ecs_vec_t dst = ecs_vec_copy(a, column, size); - int32_t count = ecs_vec_count(column); - void *dst_ptr = ecs_vec_first(&dst); - void *src_ptr = ecs_vec_first(column); + /* Load module */ + module(world); - ecs_xtor_t ctor = ti->hooks.ctor; - if (ctor) { - ctor(dst_ptr, count, ti); - } + /* Lookup module entity (must be registered by module) */ + e = ecs_lookup_fullpath(world, module_name); + ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); - copy(dst_ptr, src_ptr, count, ti); - *column = dst; - } else { - *column = ecs_vec_copy(a, column, size); - } + ecs_log_pop(); } - return result; + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->info.name_prefix = old_name_prefix; + + return e; +error: + return 0; } -static -void snapshot_table( - const ecs_world_t *world, - ecs_snapshot_t *snapshot, - ecs_table_t *table) +ecs_entity_t ecs_import_c( + ecs_world_t *world, + ecs_module_action_t module, + const char *c_name) { - 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_world_t*)world, &table->type); - l->data = flecs_duplicate_data((ecs_world_t*)world, table, &table->data); + char *name = ecs_module_path_from_c(c_name); + ecs_entity_t e = ecs_import(world, module, name); + ecs_os_free(name); + return e; } -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_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name) { - ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - - ecs_run_aperiodic((ecs_world_t*)world, 0); + ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); - result->world = (ecs_world_t*)world; + char *import_func = ECS_CONST_CAST(char*, module_name); + char *module = ECS_CONST_CAST(char*, module_name); - /* 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); + 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; } - /* 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); + /* 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 ++; + } + } + } - /* Array may have holes, so initialize with 0 */ - ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); + *bptr = '\0'; - /* Iterate tables in iterator */ - if (iter) { - while (next(iter)) { - ecs_table_t *table = iter->table; - snapshot_table(world, result, table); + 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 { - 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); - } + ecs_trace("found file '%s' for library '%s'", + library_filename, library_name); } - return result; -} + 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); -/** Create a snapshot */ -ecs_snapshot_t* ecs_snapshot_take( - ecs_world_t *stage) -{ - const ecs_world_t *world = ecs_get_world(stage); + if (module != module_name) { + ecs_os_free(module); + } - ecs_snapshot_t *result = snapshot_create( - world, ecs_eis(world), NULL, NULL); + return 0; + } else { + ecs_trace("library '%s' ('%s') loaded", + library_name, library_filename); + } - result->last_id = flecs_entities_max_id(world); + 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); + } - return result; -} + /* Do not free id, as it will be stored as the component identifier */ + ecs_entity_t result = ecs_import(world, action, module); -/** 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); + if (import_func != module_name) { + ecs_os_free(import_func); + } - ecs_snapshot_t *result = snapshot_create( - world, ecs_eis(world), iter, iter ? iter->next : NULL); + if (module != module_name) { + ecs_os_free(module); + } - result->last_id = flecs_entities_max_id(world); + ecs_os_free(library_filename); return result; +error: + return 0; } -/* Restoring an unfiltered snapshot restores the world to the exact state it was - * when the snapshot was taken. */ -static -void restore_unfiltered( +ecs_entity_t ecs_module_init( ecs_world_t *world, - ecs_snapshot_t *snapshot) + const char *c_name, + const ecs_component_desc_t *desc) { - 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); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(world, ecs_world_t); - for (i = 1; i <= count; i ++) { - ecs_table_t *world_table = flecs_sparse_get_t( - &world->store.tables, ecs_table_t, (uint32_t)i); + ecs_entity_t old_scope = ecs_set_scope(world, 0); - if (world_table && (world_table->flags & EcsTableHasBuiltins)) { - continue; - } + 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_table_leaf_t *snapshot_table = NULL; - if (i < snapshot_count) { - snapshot_table = &leafs[i]; - if (!snapshot_table->table) { - snapshot_table = NULL; - } - } + ecs_component_desc_t private_desc = *desc; + private_desc.entity = e; - /* 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 (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; + } - 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); + ecs_set_scope(world, old_scope); - 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 { } + return e; +error: + return 0; +} - if (snapshot_table) { - ecs_os_free(snapshot_table->data); - flecs_type_free(world, &snapshot_table->type); - } - } +#endif - /* 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; - } +/** + * @file addons/monitor.c + * @brief Monitor addon. + */ - int32_t tcount = ecs_table_count(table); - if (tcount) { - flecs_notify_on_set(world, table, 0, tcount, NULL, 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); +#ifdef FLECS_MONITOR - for (l = 0; l < snapshot_count; l ++) { - ecs_table_leaf_t *snapshot_table = &leafs[l]; - ecs_table_t *table = snapshot_table->table; +ECS_COMPONENT_DECLARE(FlecsMonitor); +ECS_COMPONENT_DECLARE(EcsWorldStats); +ECS_COMPONENT_DECLARE(EcsWorldSummary); +ECS_COMPONENT_DECLARE(EcsPipelineStats); - if (!table) { - continue; - } +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; - ecs_data_t *data = snapshot_table->data; - if (!data) { - flecs_type_free(world, &snapshot_table->type); - continue; - } +static int32_t flecs_day_interval_count = 24; +static int32_t flecs_week_interval_count = 168; - /* 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); - } - } +static +ECS_COPY(EcsPipelineStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); +}) - /* 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); +static +ECS_MOVE(EcsPipelineStats, dst, src, { + ecs_os_memcpy_t(dst, src, EcsPipelineStats); + ecs_os_zeromem(src); +}) - flecs_table_merge(world, table, table, &table->data, snapshot_table->data); +static +ECS_DTOR(EcsPipelineStats, ptr, { + ecs_pipeline_stats_fini(&ptr->stats); +}) - /* Run OnSet systems for merged entities */ - if (new_count) { - flecs_notify_on_set( - world, table, old_count, new_count, NULL, true); - } +static +void UpdateWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 1); - flecs_wfree_n(world, ecs_vec_t, table->storage_count, - snapshot_table->data->columns); - ecs_os_free(snapshot_table->data); - flecs_type_free(world, &snapshot_table->type); - } -} + const ecs_world_info_t *info = ecs_get_world_info(it->world); -/** 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); - } + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + summary[i].target_fps = (double)info->target_fps; - ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); + 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; - ecs_os_free(snapshot); + 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; + } } -ecs_iter_t ecs_snapshot_iter( - ecs_snapshot_t *snapshot) -{ - ecs_snapshot_iter_t iter = { - .tables = snapshot->tables, - .index = 0 - }; +static +void MonitorStats(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; - return (ecs_iter_t){ - .world = snapshot->world, - .table_count = ecs_vec_count(&snapshot->tables), - .priv.iter.snapshot = iter, - .next = ecs_snapshot_next - }; -} + 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); -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; + ecs_ftime_t elapsed = hdr->elapsed; + hdr->elapsed += it->delta_time; - for (i = iter->index; i < count; i ++) { - ecs_table_t *table = tables[i].table; - if (!table) { - continue; - } + 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_data_t *data = tables[i].data; + ecs_world_stats_t last_world = {0}; + ecs_pipeline_stats_t last_pipeline = {0}; + void *last = NULL; - it->table = table; - it->count = ecs_table_count(table); - if (data) { - it->entities = ecs_vec_first(&data->entities); - } else { - it->entities = 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); } - - 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); + 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); + } - 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); + 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); } - flecs_type_free(snapshot->world, &snapshot_table->type); } - } + hdr->reduce_count = 0; + } - ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); - ecs_os_free(snapshot); + if (last && kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_fini(last); + } } -#endif - -/** - * @file addons/system/system.c - * @brief System addon. - */ - +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); -#ifdef FLECS_SYSTEM + 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); -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) + if (kind == ecs_id(EcsWorldStats)) { + ecs_world_stats_reduce(dst, src); + } else if (kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_reduce(dst, src); } -}; +} -/* -- Public API -- */ +static +void AggregateStats(ecs_iter_t *it) { + int32_t interval = *(int32_t*)it->ctx; -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; + EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1); + EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2); - /* Support legacy behavior */ - if (!param) { - param = system_data->ctx; - } + void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); + void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); - if (tick_source) { - const EcsTickSource *tick = ecs_get( - world, tick_source, EcsTickSource); + ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); - if (tick) { - time_elapsed = tick->time_elapsed; + ecs_world_stats_t last_world = {0}; + ecs_pipeline_stats_t last_pipeline = {0}; + void *last = NULL; - /* 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 (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; } } - 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); + /* 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); } - ecs_time_t time_start; - bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); - if (measure_time) { - ecs_os_get_time(&time_start); + 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); + } } - ecs_world_t *thread_ctx = world; - if (stage) { - thread_ctx = stage->thread_ctx; - } else { - stage = &world->stages[0]; + /* A day has 60 24 minute intervals */ + dst_hdr->reduce_count ++; + if (dst_hdr->reduce_count >= interval) { + dst_hdr->reduce_count = 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 (last && kind == ecs_id(EcsPipelineStats)) { + ecs_pipeline_stats_fini(last); } +} - if (stage_count > 1 && system_data->multi_threaded) { - wit = ecs_worker_iter(it, stage_index, stage_count); - it = &wit; - } +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); - 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); - } + // 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 + }); - if (measure_time) { - system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); - } + // 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 + }); - system_data->invoke_count ++; + // 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 + }); - flecs_defer_end(world, stage); + // 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 + }); - return it->interrupted_by; -} + // 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 + }); -/* -- Public API -- */ + ecs_set_scope(world, prev); -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_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); } -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) +static +void flecs_world_monitor_import( + ecs_world_t *world) { - 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); + ECS_COMPONENT_DEFINE(world, EcsWorldStats); - return ecs_run_intern( - world, stage, system, system_data, stage_index, stage_count, - delta_time, 0, 0, param); + flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), + sizeof(EcsWorldStats)); } -ecs_entity_t ecs_run( - ecs_world_t *world, - ecs_entity_t system, - ecs_ftime_t delta_time, - void *param) +static +void flecs_pipeline_monitor_import( + ecs_world_t *world) { - return ecs_run_w_filter(world, system, delta_time, 0, 0, param); -} + ECS_COMPONENT_DEFINE(world, EcsPipelineStats); -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; - } -} + ecs_set_hooks(world, EcsPipelineStats, { + .ctor = ecs_default_ctor, + .copy = ecs_copy(EcsPipelineStats), + .move = ecs_move(EcsPipelineStats), + .dtor = ecs_dtor(EcsPipelineStats) + }); -void* ecs_get_system_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; - } + flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats), + sizeof(EcsPipelineStats)); } -void* ecs_get_system_binding_ctx( - const ecs_world_t *world, - ecs_entity_t system) +void FlecsMonitorImport( + ecs_world_t *world) { - const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); - if (s) { - return s->binding_ctx; - } else { - return NULL; - } -} + ECS_MODULE_DEFINE(world, FlecsMonitor); + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif -/* System deinitialization */ -static -void flecs_system_fini(ecs_system_t *sys) { - if (sys->ctx_free) { - sys->ctx_free(sys->ctx); - } + ecs_set_name_prefix(world, "Ecs"); - if (sys->binding_ctx_free) { - sys->binding_ctx_free(sys->binding_ctx); - } + 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_poly_free(sys, ecs_system_t); -} + ECS_COMPONENT_DEFINE(world, EcsWorldSummary); -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); +#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_entity_t entity = desc->entity; - if (!entity) { - entity = ecs_new(world, 0); + 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); } - 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; +#endif - ecs_query_t *query = ecs_query_init(world, &query_desc); - if (!query) { - ecs_delete(world, entity); - return 0; - } +/** + * @file addons/parser.c + * @brief Parser addon. + */ - /* 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; +#ifdef FLECS_PARSER - system->run = desc->run; - system->action = desc->callback; +#include - system->ctx = desc->ctx; - system->binding_ctx = desc->binding_ctx; +#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 '"' - system->ctx_free = desc->ctx_free; - system->binding_ctx_free = desc->binding_ctx_free; +#define TOK_SELF "self" +#define TOK_UP "up" +#define TOK_DOWN "down" +#define TOK_CASCADE "cascade" +#define TOK_PARENT "parent" - system->tick_source = desc->tick_source; +#define TOK_OVERRIDE "OVERRIDE" +#define TOK_ROLE_AND "AND" +#define TOK_ROLE_OR "OR" +#define TOK_ROLE_NOT "NOT" +#define TOK_ROLE_TOGGLE "TOGGLE" - system->multi_threaded = desc->multi_threaded; - system->no_readonly = desc->no_readonly; +#define TOK_IN "in" +#define TOK_OUT "out" +#define TOK_INOUT "inout" +#define TOK_INOUT_NONE "none" - if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { -#ifdef FLECS_TIMER - if (desc->interval != 0) { - ecs_set_interval(world, entity, desc->interval); - } +static +const ecs_id_t ECS_OR = (1ull << 59); - if (desc->rate) { - ecs_set_rate(world, entity, desc->rate, desc->tick_source); - } else if (desc->tick_source) { - ecs_set_tick_source(world, entity, desc->tick_source); - } -#else - ecs_abort(ECS_UNSUPPORTED, "timer module not available"); -#endif - } +static +const ecs_id_t ECS_NOT = (1ull << 58); - if (ecs_get_name(world, entity)) { - ecs_trace("#[green]system#[reset] %s created", - ecs_get_name(world, entity)); - } +#define ECS_MAX_TOKEN_SIZE (256) - ecs_defer_end(world); - } else { - ecs_system_t *system = ecs_poly(poly->poly, ecs_system_t); +typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; - 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; - } - - if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { -#ifdef FLECS_TIMER - if (desc->interval != 0) { - ecs_set_interval(world, entity, desc->interval); - } - if (desc->rate != 0) { - ecs_set_rate(world, entity, desc->rate, desc->tick_source); - } else if (desc->tick_source) { - ecs_set_tick_source(world, entity, desc->tick_source); - } -#else - ecs_abort(ECS_UNSUPPORTED, "timer module not available"); -#endif - } +const char* ecs_parse_ws_eol( + const char *ptr) +{ + while (isspace(*ptr)) { + ptr ++; } - ecs_poly_modified(world, entity, ecs_system_t); - - return entity; -error: - return 0; + return ptr; } -void FlecsSystemImport( - ecs_world_t *world) +const char* ecs_parse_ws( + const char *ptr) { - ECS_MODULE(world, FlecsSystem); - - ecs_set_name_prefix(world, "Ecs"); - - flecs_bootstrap_tag(world, EcsSystem); - flecs_bootstrap_component(world, EcsTickSource); + while ((*ptr != '\n') && isspace(*ptr)) { + ptr ++; + } - /* 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); + return ptr; } -#endif - -/** - * @file json/json.c - * @brief JSON serializer utilities. - */ - -/** - * @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, - JsonLargeString, - JsonInvalid -} ecs_json_token_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* ecs_parse_digit( + const char *ptr, + char *token) +{ + char *tptr = token; + char ch = ptr[0]; -const char* flecs_json_expect_member_name( - const char *json, - char *token, - const char *member_name, - const ecs_from_json_desc_t *desc); + if (!isdigit(ch) && ch != '-') { + ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); + return NULL; + } -const char* flecs_json_skip_object( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); + tptr[0] = ch; + tptr ++; + ptr ++; -const char* flecs_json_skip_array( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { + break; + } -/* Serialize to JSON */ -void flecs_json_next( - ecs_strbuf_t *buf); + tptr[0] = ch; + tptr ++; + } -void flecs_json_number( - ecs_strbuf_t *buf, - double value); + tptr[0] = '\0'; + + return ptr; +} -void flecs_json_true( - ecs_strbuf_t *buf); +/* -- Private functions -- */ -void flecs_json_false( - ecs_strbuf_t *buf); +bool flecs_isident( + char ch) +{ + return isalpha(ch) || (ch == '_'); +} -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value); +static +bool flecs_valid_identifier_start_char( + char ch) +{ + if (ch && (flecs_isident(ch) || (ch == '*') || + (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) + { + return true; + } -void flecs_json_array_push( - ecs_strbuf_t *buf); + return false; +} -void flecs_json_array_pop( - ecs_strbuf_t *buf); +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; + } -void flecs_json_object_push( - ecs_strbuf_t *buf); + return false; +} -void flecs_json_object_pop( - ecs_strbuf_t *buf); +static +bool flecs_valid_token_char( + char ch) +{ + if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { + return true; + } -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value); + return false; +} -void flecs_json_string_escape( - ecs_strbuf_t *buf, - const char *value); +static +bool flecs_valid_operator_char( + char ch) +{ + if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + return true; + } -void flecs_json_member( - ecs_strbuf_t *buf, - const char *name); + return false; +} -void flecs_json_membern( - ecs_strbuf_t *buf, +const char* ecs_parse_token( const char *name, - int32_t name_len); + const char *expr, + const char *ptr, + char *token_out, + char delim) +{ + int64_t column = ptr - expr; -#define flecs_json_memberl(buf, name)\ - flecs_json_membern(buf, name, sizeof(name) - 1) + ptr = ecs_parse_ws(ptr); + char *tptr = token_out, ch = ptr[0]; -void flecs_json_path( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); + 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; + } -void flecs_json_label( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); + tptr[0] = ch; + tptr ++; + ptr ++; -void flecs_json_color( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); + if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { + tptr[0] = 0; + return ptr; + } -void flecs_json_id( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_id_t id); + int tmpl_nesting = 0; + bool in_str = ch == '"'; -ecs_primitive_kind_t flecs_json_op_to_primitive_kind( - ecs_meta_type_op_kind_t kind); + 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; + } -#endif + tptr[0] = ch; + tptr ++; + } -#include + tptr[0] = '\0'; -#ifdef FLECS_JSON + if (tmpl_nesting != 0) { + ecs_parser_error(name, expr, column, + "identifier '%s' has mismatching < > pairs", ptr); + return NULL; + } -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 JsonString: return "string"; - case JsonTrue: return "true"; - case JsonFalse: return "false"; - case JsonNull: return "null"; - case JsonInvalid: return "invalid"; - default: - ecs_abort(ECS_INTERNAL_ERROR, 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 "invalid"; + + return ptr; } -const char* flecs_json_parse( - const char *json, - ecs_json_token_t *token_kind, - char *token) +const char* ecs_parse_identifier( + const char *name, + const char *expr, + const char *ptr, + char *token_out) { - json = ecs_parse_ws_eol(json); - - char ch = json[0]; + if (!flecs_valid_identifier_start_char(ptr[0]) && (ptr[0] != '"')) { + ecs_parser_error(name, expr, (ptr - expr), + "expected start of identifier"); + return NULL; + } - 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; - } + ptr = ecs_parse_token(name, expr, ptr, token_out, 0); - 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; - } + return ptr; +} - json = ecs_chrparse(json, token_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 ++; } + } - if (!ch) { - token_kind[0] = JsonInvalid; - return NULL; + 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 { - token_kind[0] = JsonString; - return json; - } - } else if (isdigit(ch) || (ch == '-')) { - token_kind[0] = JsonNumber; - return ecs_parse_digit(json, token); - } 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; + name[len - 1] = '\0'; } + } - if (isalpha(json[0]) || isdigit(json[0])) { - token_kind[0] = JsonInvalid; - return NULL; - } + return 0; +} - return json; +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 { - token_kind[0] = JsonInvalid; - return NULL; + ecs_parser_error(name, sig, column, "invalid role '%s'", token); + return 0; } } -const char* flecs_json_parse_large_string( - const char *json, - ecs_strbuf_t *buf) +static +ecs_oper_kind_t flecs_parse_operator( + char ch) { - if (json[0] != '"') { - return NULL; /* can only parse strings */ + if (ch == TOK_OPTIONAL) { + return EcsOptional; + } else if (ch == TOK_NOT) { + return EcsNot; + } else { + ecs_throw(ECS_INTERNAL_ERROR, NULL); } +error: + return 0; +} - char ch, ch_out; - json ++; - for (; (ch = json[0]); ) { - if (ch == '"') { - json ++; - break; - } +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]; - json = ecs_chrparse(json, &ch_out); - ecs_strbuf_appendch(buf, ch_out); + ptr = ecs_parse_identifier(name, sig, ptr, token); + if (!ptr) { + return NULL; } - if (!ch) { - return NULL; - } else { - return json; + 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; } -} -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; -} + ptr = ecs_parse_ws(ptr); -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) { + if (ptr[0] != TOK_BRACKET_CLOSE) { + ecs_parser_error(name, sig, column, "expected ]"); return NULL; } - return json; + + return ptr + 1; } -const char* flecs_json_expect_member_name( - const char *json, - char *token, - const char *member_name, - const ecs_from_json_desc_t *desc) +static +uint8_t flecs_parse_set_token( + const char *token) { - 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; + 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_PARENT)) { + return EcsParent; + } else { + return 0; } - return json; } -const char* flecs_json_skip_object( - const char *json, +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, - const ecs_from_json_desc_t *desc) + ecs_term_id_t *id, + char tok_end) { - 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 }"); + 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; } } - ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }"); - 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; + } -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; + if (id->flags & tok) { + ecs_parser_error(name, expr, column, + "duplicate set token '%s'", token); + return NULL; + } + + id->flags |= tok; - 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; + 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; + } + } } - } - ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); - 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; + } + } -void flecs_json_next( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_next(buf); -} + /* End of set expression */ + } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { + break; + } + } while (true); -void flecs_json_number( - ecs_strbuf_t *buf, - double value) -{ - ecs_strbuf_appendflt(buf, value, '"'); + return ptr; } -void flecs_json_true( - ecs_strbuf_t *buf) +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_strbuf_appendlit(buf, "true"); -} + (void)column; -void flecs_json_false( - ecs_strbuf_t *buf) -{ - ecs_strbuf_appendlit(buf, "false"); -} + int32_t arg = 0; -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value) -{ - if (value) { - flecs_json_true(buf); - } else { - flecs_json_false(buf); - } -} + do { + if (flecs_valid_token_start_char(ptr[0])) { + if (arg == 2) { + ecs_parser_error(name, expr, (ptr - expr), + "too many arguments in term"); + return NULL; + } -void flecs_json_array_push( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_push(buf, "[", ", "); -} + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } -void flecs_json_array_pop( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_pop(buf, "]"); -} + ecs_term_id_t *term_id = NULL; -void flecs_json_object_push( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_push(buf, "{", ", "); -} + if (arg == 0) { + term_id = &term->src; + } else if (arg == 1) { + term_id = &term->second; + } -void flecs_json_object_pop( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_pop(buf, "}"); -} + /* 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); + return NULL; + } -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value) -{ - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, value); - ecs_strbuf_appendch(buf, '"'); -} + ptr = ecs_parse_ws(ptr + 1); + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, + NULL, term_id, TOK_PAREN_CLOSE); + if (!ptr) { + return NULL; + } -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_zerocpy(buf, out); - } -} + /* Check for term flags */ + } else if (!ecs_os_strcmp(token, TOK_CASCADE) || + !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) { + return NULL; + } -void flecs_json_member( - ecs_strbuf_t *buf, - const char *name) -{ - flecs_json_membern(buf, name, ecs_os_strlen(name)); -} + /* Regular identifier */ + } else if (flecs_parse_identifier(token, term_id)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } -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, "\":"); -} + if (ptr[0] == TOK_AND) { + ptr = ecs_parse_ws(ptr + 1); -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, '"'); -} + term->id_flags = ECS_PAIR; -void flecs_json_label( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - const char *lbl = NULL; -#ifdef FLECS_DOC - lbl = ecs_doc_get_name(world, e); -#else - lbl = ecs_get_name(world, e); -#endif + } else if (ptr[0] == TOK_PAREN_CLOSE) { + ptr = ecs_parse_ws(ptr + 1); + break; - if (lbl) { - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, lbl); - ecs_strbuf_appendch(buf, '"'); - } else { - ecs_strbuf_appendch(buf, '0'); - } -} + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected ',' or ')'"); + return NULL; + } -void flecs_json_color( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - (void)world; - (void)e; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier or set expression"); + return NULL; + } - const char *color = NULL; -#ifdef FLECS_DOC - color = ecs_doc_get_color(world, e); -#endif + arg ++; - if (color) { - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, color); - ecs_strbuf_appendch(buf, '"'); + } while (true); + + return ptr; +} + +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_strbuf_appendch(buf, '0'); + ecs_parser_error(name, expr, (ptr - expr), + "unexpected end of term"); } } -void flecs_json_id( - ecs_strbuf_t *buf, +static +const char* flecs_parse_term( const ecs_world_t *world, - ecs_id_t id) + const char *name, + const char *expr, + ecs_term_t *term_out) { - ecs_strbuf_appendch(buf, '['); + const char *ptr = expr; + char token[ECS_MAX_TOKEN_SIZE] = {0}; + ecs_term_t term = { .move = true /* parser never owns resources */ }; - 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, '"'); + 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(ptr); } - ecs_strbuf_appendch(buf, ']'); -} + if (flecs_valid_operator_char(ptr[0])) { + term.oper = flecs_parse_operator(ptr[0]); + ptr = ecs_parse_ws(ptr + 1); + } -ecs_primitive_kind_t flecs_json_op_to_primitive_kind( - ecs_meta_type_op_kind_t kind) -{ - return kind - EcsOpPrimitive; -} + /* 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; + } -#endif + /* Is token a type role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr ++; + goto flecs_parse_role; + } -/** - * @file json/serialize.c - * @brief Serialize (component) values to JSON strings. - */ + /* Is token a predicate? */ + if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_predicate; + } + /* Next token must be a predicate */ + goto parse_predicate; -#ifdef FLECS_JSON + /* Pair with implicit subject */ + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; -/* Cached id records during serialization */ -typedef struct ecs_json_ser_idr_t { - ecs_id_record_t *idr_doc_name; - ecs_id_record_t *idr_doc_color; -} ecs_json_ser_idr_t; + /* 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; -static -int json_ser_type( - const ecs_world_t *world, - const ecs_vec_t *ser, - const void *base, - ecs_strbuf_t *str); - -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); + /* 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; -/* 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); + /* Nothing else expected here */ + } else { + flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); + goto error; + } - int32_t value = *(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); +flecs_parse_role: + term.id_flags = flecs_parse_role(name, expr, (ptr - expr), token); + if (!term.id_flags) { goto error; } - ecs_strbuf_appendch(str, '"'); - ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); - ecs_strbuf_appendch(str, '"'); + ptr = ecs_parse_ws(ptr); - return 0; -error: - return -1; -} + /* 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; + } -/* 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); + /* If not, it's a predicate */ + goto parse_predicate; - uint32_t value = *(uint32_t*)ptr; - if (!value) { - ecs_strbuf_appendch(str, '0'); - return 0; + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after role"); + goto error; } - ecs_strbuf_list_push(str, "\"", "|"); +parse_predicate: + if (flecs_parse_identifier(token, &term.first)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } - /* 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; + /* 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 (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; + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + 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); + } + + goto parse_done; } - ecs_strbuf_list_pop(str, "\""); + goto parse_done; - return 0; -error: - return -1; -} +parse_eq: + term.src = term.first; + term.first = (ecs_term_id_t){0}; + term.first.id = EcsPredEq; + goto parse_right_operand; -/* 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); +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; - const void *ptr = base; +parse_right_operand: + if (flecs_valid_token_start_char(ptr[0])) { + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + goto error; + } - 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; + if (term.first.id == EcsPredMatch) { + if (token[0] == '"' && token[1] == '!') { + term.oper = EcsNot; + } } - ptr = ECS_OFFSET(ptr, elem_size); - } - flecs_json_array_pop(str); + if (flecs_parse_identifier(token, &term.second)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } - return 0; -} + 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; + } -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); + 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; + } + } - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + 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; + } - 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); +parse_pair_predicate: + if (flecs_parse_identifier(token, &term.first)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } - return json_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str, is_array); -} + 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; + } -/* 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); + 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; + } + } - return json_ser_type_elements( - world, a->type, ptr, a->count, str, true); -} + if (ptr[0] == TOK_PAREN_CLOSE) { + ptr ++; + 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; + } -/* 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); +parse_pair_object: + if (flecs_parse_identifier(token, &term.second)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + goto error; + } - int32_t count = ecs_vec_count(value); - void *array = ecs_vec_first(value); + if (term.id_flags == 0) { + term.id_flags = ECS_PAIR; + } - /* Serialize contiguous buffer of vector */ - return json_ser_type_elements(world, v->type, array, count, str, false); -} + ptr = ecs_parse_ws(ptr); + goto parse_done; -typedef struct json_serializer_ctx_t { - ecs_strbuf_t *str; - bool is_collection; - bool is_struct; -} json_serializer_ctx_t; +parse_done: + *term_out = term; + return ptr; -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); +error: + ecs_term_fini(&term); + *term_out = (ecs_term_t){0}; + return NULL; } static -int json_ser_custom_member( - const ecs_serializer_t *ser, - const char *name) +bool flecs_is_valid_end_of_term( + const char *ptr) { - 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; + 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] == ':') || /* inheritance (in plecs) */ + (ptr[0] == '=')) /* assignment (in plecs) */ + { + return true; } - flecs_json_member(json_ser->str, name); - return 0; + return false; } -static -int json_ser_custom_type( +char* ecs_parse_term( const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term) { - 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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_type_kind_t kind = pt->kind; - bool is_collection = false; - bool is_struct = false; + ecs_term_id_t *src = &term->src; - 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; + 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"); + } + } } - json_serializer_ctx_t json_ser = { - .str = str, - .is_struct = is_struct, - .is_collection = is_collection - }; + ptr = ecs_parse_ws_eol(ptr); + if (!ptr[0]) { + *term = (ecs_term_t){0}; + return ECS_CONST_CAST(char*, ptr); + } - ecs_serializer_t ser = { - .world = world, - .value = json_ser_custom_value, - .member = json_ser_custom_member, - .ctx = &json_ser - }; + if (ptr == expr && !strcmp(expr, "0")) { + return ECS_CONST_CAST(char*, &ptr[1]); + } - if (ct->serialize(&ser, base)) { - return -1; + /* Parse next element */ + ptr = flecs_parse_term(world, name, ptr, term); + if (!ptr) { + goto error; } - if (kind == EcsStructType) { - flecs_json_object_pop(str); - } else if (kind == EcsArrayType || kind == EcsVectorType) { - flecs_json_array_pop(str); + /* 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; + } + + term->second.name = ecs_os_strdup(term->first.name); + term->second.flags |= EcsIsVariable; + } } - return 0; -} + /* Post-parse consistency checks */ -/* 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) -{ - 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)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); - break; - case EcsOpF64: - ecs_strbuf_appendflt(str, - *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); - break; - case EcsOpEnum: - if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { + /* 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; } - break; - case EcsOpBitmask: - if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { + + 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; } - break; - case EcsOpArray: - if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { + } + + /* 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; } - break; - case EcsOpVector: - if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { + + if (src->flags != 0) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid combination of 0 with non-default subject"); goto error; } - break; - case EcsOpOpaque: - if (json_ser_custom_type(world, op, ECS_OFFSET(ptr, op->offset), str)) { + + 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; } - break; - case EcsOpEntity: { - ecs_entity_t e = *(ecs_entity_t*)ECS_OFFSET(ptr, op->offset); - if (!e) { - ecs_strbuf_appendch(str, '0'); - } else { - flecs_json_path(str, world, e); - } - break; } - default: - if (ecs_primitive_to_expr_buf(world, - flecs_json_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str)) - { - /* Unknown operation */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - return -1; + /* 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; + } } - break; } - return 0; + 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; + } + + ptr = ecs_parse_ws(ptr); + + return ECS_CONST_CAST(char*, ptr); error: - return -1; + if (term) { + ecs_term_fini(term); + } + return NULL; } -/* 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]; +#endif - if (in_array <= 0) { - if (op->name) { - flecs_json_member(str, op->name); - } +/** + * @file addons/plecs.c + * @brief Plecs addon. + */ - 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; - default: - if (json_ser_type_op(world, op, base, str)) { - goto error; - } - break; - } - } +#ifdef FLECS_PLECS - return 0; -error: - return -1; -} +ECS_COMPONENT_DECLARE(EcsScript); -/* Iterate over the type ops of a type */ -static -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); -} +#include -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; +#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" - flecs_json_array_push(buf); +#define STACK_MAX_SIZE (64) - do { - ecs_strbuf_list_next(buf); - if (json_ser_type(world, &ser->ops, ptr, buf)) { - return -1; - } +typedef struct { + ecs_value_t value; + bool owned; +} plecs_with_value_t; - ptr = ECS_OFFSET(ptr, size); - } while (-- count); +typedef struct { + const char *name; + const char *code; - flecs_json_array_pop(buf); - } else { - if (json_ser_type(world, &ser->ops, ptr, buf)) { - return -1; - } - } + ecs_entity_t last_predicate; + ecs_entity_t last_subject; + ecs_entity_t last_object; - return 0; -} + ecs_id_t last_assign_id; + ecs_entity_t assign_to; -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; - } + 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; - 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; - } + char *annot[STACK_MAX_SIZE]; + int32_t annot_count; - return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); -} + ecs_vars_t vars; + char var_name[256]; + ecs_entity_t var_type; -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; + 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; - if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; + 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); } - return ecs_strbuf_get(&str); + ecs_vec_fini_t(NULL, &ptr->prop_defaults, ecs_value_t); } -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); -} +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; +}) -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 +ECS_DTOR(EcsScript, ptr, { + flecs_dtor_script(ptr); +}) +/* Assembly ctor to initialize with default property values */ 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) +void flecs_assembly_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ti) { - bool is_base = ent != inst; - ecs_entity_t pred = 0, obj = 0, role = 0; - bool hidden = false; + ecs_world_t *world = ti->hooks.ctx; + ecs_entity_t assembly = ti->component; + const EcsStruct *st = ecs_get(world, assembly, EcsStruct); - 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 (!st) { + ecs_err("assembly '%s' is not a struct, cannot construct", ti->name); + return; } - if (!desc || !desc->serialize_meta_ids) { - if (pred == EcsIsA || pred == EcsChildOf || - pred == ecs_id(EcsIdentifier)) - { - return true; - } -#ifdef FLECS_DOC - if (pred == ecs_id(EcsDocDescription)) { - return true; - } -#endif + const EcsScript *script = ecs_get(world, assembly, EcsScript); + if (!script) { + ecs_err("assembly '%s' is not a script, cannot construct", ti->name); + return; } - if (is_base) { - if (ecs_has_id(world, pred, EcsDontInherit)) { - return true; - } + 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; } - if (!desc || !desc->serialize_private) { - if (ecs_has_id(world, pred, EcsPrivate)) { - return true; + + 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; } - } - if (is_base) { - if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { - hidden = true; + + 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); } } - 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; } +/* Assembly on_set handler to update contents for new property values */ 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 flecs_assembly_on_set( + ecs_iter_t *it) { - (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; + if (it->table->flags & EcsTableIsPrefab) { + /* Don't instantiate assemblies for prefabs */ + return; } - 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; - } - } + 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); - if (desc && desc->serialize_id_labels) { - flecs_json_next(buf); + 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; + } - 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); - } + const EcsStruct *st = ecs_record_get(world, r, EcsStruct); + if (!st) { + ecs_err("assembly '%s' is not a struct", name); + return; + } - flecs_json_array_pop(buf); - } + const EcsScript *script = ecs_record_get(world, r, EcsScript); + if (!script) { + ecs_err("assembly '%s' is missing a script", name); + return; } - flecs_json_array_pop(buf); -#endif - return 0; -} + void *data = ecs_field_w_size(it, flecs_ito(size_t, ct->size), 1); -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; - } + 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); - flecs_json_memberl(buf, "values"); - flecs_json_array_push(buf); + /* 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]; - 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; + 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; } - 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); + /* 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; - 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); - } + /* 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); } - } - flecs_json_array_pop(buf); - - return 0; + data = ECS_OFFSET(data, ct->size); + } } +/* Delete contents of assembly instance */ 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) +void flecs_assembly_on_remove( + ecs_iter_t *it) { - if (!desc || !desc->serialize_type_info) { - return 0; + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t instance = it->entities[i]; + ecs_script_clear(it->world, 0, instance); } +} - flecs_json_memberl(buf, "type_info"); - flecs_json_array_push(buf); +/* 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); - 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; + 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 (!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); - } + 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 */ } - flecs_json_array_pop(buf); - return 0; } +/* Create new assembly */ 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) +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) { - if (!desc || !desc->serialize_hidden) { - return 0; + 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; } - if (ent == inst) { - return 0; /* if this is not a base, components are never hidden */ - } + ecs_add_id(world, assembly, EcsAlwaysOverride); - flecs_json_memberl(buf, "hidden"); - flecs_json_array_push(buf); + 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); - int32_t i; + 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 ++) { - 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; - } + ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = + state->using[i]; + } - flecs_json_next(buf); - flecs_json_bool(buf, hidden); + if (flecs_assembly_init_defaults( + world, name, expr, ptr, assembly, script, state)) + { + return -1; } - flecs_json_array_pop(buf); - + 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 -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) +bool plecs_is_newline_comment( + const char *ptr) { - 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 (ptr[0] == '/' && ptr[1] == '/') { + return true; } + return false; +} - flecs_json_memberl(buf, "ids"); - flecs_json_array_push(buf); +static +const char* plecs_parse_fluff( + const char *ptr) +{ + do { + /* Skip whitespaces before checking for a comment */ + ptr = ecs_parse_ws(ptr); - 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; - } + /* Newline comment, skip until newline character */ + if (plecs_is_newline_comment(ptr)) { + ptr += 2; - 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; + while (ptr[0] && ptr[0] != TOK_NEWLINE) { + ptr ++; } } - 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)); - } + /* If a newline character is found, skip it */ + if (ptr[0] == TOK_NEWLINE) { + ptr ++; } - 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; - } + } while (isspace(ptr[0]) || plecs_is_newline_comment(ptr)); - return 0; + return ptr; } 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) +ecs_entity_t plecs_lookup( + const ecs_world_t *world, + const char *path, + plecs_state_t *state, + ecs_entity_t rel, + bool is_subject) { - 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; - } + ecs_entity_t e = 0; - 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; + 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; } } } - 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; + if (!e) { + e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); } - flecs_json_object_pop(buf); - - return 0; + return e; } -int ecs_entity_to_json_buf( +/* Lookup action used for deserializing entity refs in component values */ +static +ecs_entity_t plecs_lookup_action( const ecs_world_t *world, - ecs_entity_t entity, - ecs_strbuf_t *buf, - const ecs_entity_to_json_desc_t *desc) + const char *path, + void *ctx) { - 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); - } + return plecs_lookup(world, path, ctx, 0, false); +} -#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); +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 { - 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); + ecs_add_id(world, e, id); } } +} - 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); - } +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; } - 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); - } + 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; } -#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 (!e) { + e = plecs_lookup(world, path, state, rel, is_subject); } - 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; - } - } - } + 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; + } - flecs_json_array_pop(buf); + 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); } - } - if (flecs_json_append_type(world, buf, entity, entity, desc)) { - goto error; - } + e = ecs_add_path(world, e, 0, path); + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); - if (desc && desc->serialize_alerts) { -#ifdef FLECS_ALERTS - const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); - if (alerts) { - flecs_json_memberl(buf, "alerts"); - flecs_json_array_push(buf); - 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 a = ecs_map_key(&it); - 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, a, ecs_id(EcsAlert), 0); - const char *severity = ecs_get_name(world, severity_id); - - const EcsAlertInstance *alert = ecs_get( - world, ai, EcsAlertInstance); - if (alert) { - flecs_json_memberl(buf, "message"); - flecs_json_string(buf, alert->message); - flecs_json_memberl(buf, "severity"); - flecs_json_string(buf, severity); - } - flecs_json_object_pop(buf); - } - flecs_json_array_pop(buf); + if (prev_scope) { + ecs_set_scope(world, prev_scope); } -#endif - } - - flecs_json_object_pop(buf); + 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); + } - return 0; -error: - return -1; -} + ecs_entity_t with = ecs_get_with(world); + if (with) { + ecs_add_id(world, e, with); + } + } + } -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 (is_new) { + if (state->assembly && !state->assembly_instance) { + ecs_add_id(world, e, EcsPrefab); + } - if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { - ecs_strbuf_reset(&buf); - return NULL; + if (state->global_with) { + ecs_add_id(world, e, state->global_with); + } } - return ecs_strbuf_get(&buf); + return e; } static -bool flecs_json_skip_variable( - const char *name) +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) { - if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { - return true; + 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 { - return false; + result = plecs_ensure_entity(world, state, name, pred, is_subject); } + return result; } static -void flecs_json_serialize_id( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) +bool plecs_pred_is_subj( + ecs_term_t *term, + plecs_state_t *state) { - flecs_json_id(buf, world, id); + 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 -void flecs_json_serialize_iter_ids( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +const char* plecs_set_mask_to_name( + ecs_flags32_t flags) { - int32_t field_count = it->field_count; - if (!field_count) { - return; + 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; +} - 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); +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 --; } - - flecs_json_array_pop(buf); + return annot; } static -void flecs_json_serialize_id_str( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) +void plecs_apply_annotations( + ecs_world_t *world, + ecs_entity_t subj, + plecs_state_t *state) { - 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); + (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); + } } - ecs_strbuf_appendch(buf, '"'); +#else + ecs_warn("cannot apply annotations, doc addon is missing"); +#endif } static -void flecs_json_serialize_type_info( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +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) { - int32_t field_count = it->field_count; - if (!field_count) { - return; - } + state->last_subject = 0; + state->last_predicate = 0; + state->last_object = 0; + state->last_assign_id = 0; - if (it->flags & EcsIterNoData) { - return; + const char *subj_name = term->src.name; + if (!subj_name) { + subj_name = plecs_set_mask_to_name(term->src.flags); } - 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"); - } + if (!ecs_term_id_is_set(&term->first)) { + ecs_parser_error(name, expr, column, "missing term in expression"); + return -1; } - flecs_json_object_pop(buf); -} + if (state->assign_stmt && !ecs_term_match_this(term)) { + ecs_parser_error(name, expr, column, + "invalid statement in assign statement"); + return -1; + } -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; + 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; + } - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (flecs_json_skip_variable(var_name)) continue; + subj = plecs_ensure_entity(world, state, subj_name, pred, true); - if (!actual_count) { - flecs_json_memberl(buf, "vars"); - flecs_json_array_push(buf); - actual_count ++; + 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; } + } - ecs_strbuf_list_next(buf); - flecs_json_string(buf, var_name); + if (state->assign_stmt || state->isa_stmt) { + subj = state->assign_to; } - if (actual_count) { - flecs_json_array_pop(buf); + if (state->isa_stmt && obj) { + ecs_parser_error(name, expr, column, + "invalid object in inheritance statement"); + return -1; } -} -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); + if (state->isa_stmt) { + pred = ecs_pair(EcsIsA, pred); + } - 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); + if (subj == EcsVariable) { + subj = pred; } - flecs_json_array_pop(buf); -} + 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); -static -void flecs_json_serialize_iter_result_table_type( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - if (!it->table) { - return; + pred_as_subj = false; + } else { + if (!obj) { + /* If no subject or object were provided, use predicate as subj + * unless the expression explictly 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; + } } - flecs_json_memberl(buf, "ids"); - flecs_json_array_push(buf); + /* 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; - ecs_type_t *type = &it->table->type; - for (int i = 0; i < type->count; i ++) { - flecs_json_next(buf); - flecs_json_serialize_id(world, type->array[i], buf); - } + if (obj) { + id = ecs_pair(pred, obj); + } else { + id = pred; + } - flecs_json_array_pop(buf); -} + state->with[state->with_frame ++] = id; + } else { + if (subj && !state->scope_assign_stmt) { + plecs_apply_with_frame(world, state, subj); + } + } -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); + /* 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); + } - 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'); + /* 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); } - flecs_json_array_pop(buf); + return 0; } static -void flecs_json_serialize_iter_result_is_set( - const ecs_iter_t *it, - ecs_strbuf_t *buf) +const char* plecs_parse_inherit_stmt( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - if (!(it->flags & EcsIterHasCondSet)) { - return; + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "cannot nest inheritance"); + return NULL; } - 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); - } + 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; - flecs_json_array_pop(buf); + return ptr; } static -void flecs_json_serialize_iter_result_variables( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +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) { - 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 = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (flecs_json_skip_variable(var_name)) continue; + ecs_value_t value = {0}; - if (!actual_count) { - flecs_json_memberl(buf, "vars"); - flecs_json_array_push(buf); - actual_count ++; + 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); } + } - ecs_strbuf_list_next(buf); - flecs_json_path(buf, world, variables[i].entity); + 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 (actual_count) { - flecs_json_array_pop(buf); + 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 -void flecs_json_serialize_iter_result_variable_labels( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +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) { - 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 = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (flecs_json_skip_variable(var_name)) continue; + (void)world; + + if (state->var_stmt) { + return plecs_parse_assign_var_expr(world, name, expr, ptr, state, var); + } - if (!actual_count) { - flecs_json_memberl(buf, "var_labels"); - flecs_json_array_push(buf); - actual_count ++; - } + if (!state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "unexpected value outside of assignment statement"); + return NULL; + } - ecs_strbuf_list_next(buf); - flecs_json_label(buf, world, variables[i].entity); + 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; } - if (actual_count) { - flecs_json_array_pop(buf); + ecs_entity_t assign_to = state->assign_to; + if (!assign_to) { + assign_to = state->last_subject; } -} -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 = 0; 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 ++; - } + if (!assign_to) { + ecs_parser_error(name, expr, ptr - expr, + "missing entity to assign to"); + return NULL; + } - ecs_strbuf_list_next(buf); - flecs_json_number(buf, (double)variables[i].entity); + 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 (actual_count) { - flecs_json_array_pop(buf); + if (assign_to == EcsVariable) { + assign_to = type; } -} -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); + void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); - EcsIdentifier *names = ecs_table_get_id(it->world, it->table, - ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); - if (!names) { - return false; + 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; } - int i; - for (i = 0; i < it->count; i ++) { - flecs_json_next(buf); - flecs_json_string(buf, names[i].value); - } + ecs_modified_id(world, assign_to, assign_id); - return true; + return ptr; } static -void flecs_json_serialize_iter_result_entity_ids( - const ecs_iter_t *it, - ecs_strbuf_t *buf) +const char* plecs_parse_assign_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - if (!it->count) { - return; - } + (void)world; - flecs_json_memberl(buf, "entity_ids"); - flecs_json_array_push(buf); + state->isa_stmt = false; - ecs_entity_t *entities = it->entities; + /* 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; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_number(buf, (double)(uint32_t)entities[i]); + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid assign statement in assign statement"); + return NULL; } - flecs_json_array_pop(buf); -} + state->assign_stmt = true; + + /* Assignment without a preceding component */ + if (ptr[0] == '{') { + ecs_entity_t type = 0; -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; - } + /* 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; + } + } - const ecs_table_record_t *tr = flecs_id_record_get_table( - world->idr_childof_wildcard, it->table); - if (tr == NULL) { - return; + /* 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; } - ecs_id_t id = table->type.array[tr->column]; - 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); + return ptr; } static -void flecs_json_serialize_iter_result_entities( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +const char* plecs_parse_assign_with_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - if (!it->count) { - return; + 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; } - flecs_json_serialize_iter_result_parent(world, it, buf); - - flecs_json_memberl(buf, "entities"); - flecs_json_array_push(buf); + 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; + } - if (!flecs_json_serialize_iter_result_entity_names(it, buf)) { - ecs_entity_t *entities = it->entities; + 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; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_number(buf, (double)(uint32_t)entities[i]); - } + 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; } - flecs_json_array_pop(buf); + return ptr; } static -void flecs_json_serialize_iter_result_entity_labels( - const ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_json_ser_idr_t *ser_idr) +const char* plecs_parse_assign_with_var( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - (void)buf; - (void)ser_idr; - if (!it->count) { - return; - } + ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); + ecs_assert(state->with_stmt, ECS_INTERNAL_ERROR, NULL); - if (!ser_idr->idr_doc_name) { - return; + 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; } -#ifdef FLECS_DOC - const ecs_table_record_t *tr = flecs_id_record_get_table( - ser_idr->idr_doc_name, it->table); - if (tr == NULL) { - return; + 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; } - EcsDocDescription *labels = ecs_table_get_column( - it->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(buf, labels[i].value); - } + 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 ++; - flecs_json_array_pop(buf); -#endif + return ptr; } static -void flecs_json_serialize_iter_result_colors( - const ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_json_ser_idr_t *ser_idr) +const char* plecs_parse_var_as_component( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - (void)buf; - (void)ser_idr; - - if (!it->count) { - return; + 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; } -#ifdef FLECS_DOC - if (!ser_idr->idr_doc_color) { - return; + 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; } - const ecs_table_record_t *tr = flecs_id_record_get_table( - ser_idr->idr_doc_color, it->table); - if (tr == NULL) { - return; + if (!state->assign_to) { + ecs_parser_error(name, expr, ptr - expr, + "missing lvalue for variable assignment '%s'", var_name); + return NULL; } - EcsDocDescription *colors = ecs_table_get_column( - it->table, tr->column, it->offset); - ecs_assert(colors != NULL, ECS_INTERNAL_ERROR, 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; + } - flecs_json_memberl(buf, "colors"); - flecs_json_array_push(buf); + 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; + } - int i; - for (i = 0; i < it->count; i ++) { - flecs_json_next(buf); - flecs_json_string(buf, colors[i].value); + 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; } - flecs_json_array_pop(buf); -#endif + ecs_modified_id(world, assign_to, type); + + return ptr; } static -int flecs_json_serialize_iter_result_values( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void plecs_push_using( + ecs_entity_t scope, + plecs_state_t *state) { - if (!it->ptrs || (it->flags & EcsIterNoData)) { - return 0; + for (int i = 0; i < state->using_frame; i ++) { + if (state->using[i] == scope) { + return; + } } - 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); + state->using[state->using_frame ++] = scope; +} - const void *ptr = NULL; - if (it->ptrs) { - ptr = it->ptrs[i]; - } +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; + } - 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; - } - } + 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; + } - if (ecs_field_is_writeonly(it, i + 1)) { - ecs_strbuf_appendch(buf, '0'); - continue; - } + 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; + } - /* 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; - } + /* Lookahead as * is not matched by parse_token */ + if (ptr[0] == '*') { + using_path[len] = '*'; + using_path[len + 1] = '\0'; + len ++; + ptr ++; + } - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (!comp) { - /* Also odd, typeid but not a component? */ - ecs_strbuf_appendch(buf, '0'); - continue; + 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; } - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - /* Not odd, component just has no reflection data */ - ecs_strbuf_appendch(buf, '0'); - continue; + /* 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); + } } - - /* 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)) { - ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL); - flecs_json_array_push(buf); - flecs_json_array_pop(buf); - continue; + } 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; } - 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; - } - } + plecs_push_using(scope, state); } - flecs_json_array_pop(buf); - - return 0; + state->using_frames[state->sp] = state->using_frame; + return ptr; } static -int flecs_json_serialize_iter_result_columns( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +const char* plecs_parse_module_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_table_t *table = it->table; - if (!table || !table->storage_table) { - return 0; + 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; } - flecs_json_memberl(buf, "values"); - flecs_json_array_push(buf); - - ecs_type_t *type = &table->type; - int32_t *storage_map = table->storage_map; - ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, 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; + } - for (int i = 0; i < type->count; i ++) { - int32_t storage_column = -1; - if (storage_map) { - storage_column = storage_map[i]; - } + 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; + } - ecs_strbuf_list_next(buf); + state->is_module = true; + state->sp ++; + state->scope[state->sp] = module; + ecs_set_scope(world, module); + return ptr; +} - if (storage_column == -1) { - ecs_strbuf_appendch(buf, '0'); - continue; - } +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; + } - ecs_entity_t typeid = table->type_info[storage_column]->component; - if (!typeid) { - ecs_strbuf_appendch(buf, '0'); - continue; - } + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with in assign_stmt"); + return NULL; + } - const EcsComponent *comp = ecs_get(world, typeid, EcsComponent); - if (!comp) { - ecs_strbuf_appendch(buf, '0'); - continue; - } + /* Add following expressions to with list */ + state->with_stmt = true; + return ptr + 5; +} - const EcsMetaTypeSerialized *ser = ecs_get( - world, typeid, EcsMetaTypeSerialized); - if (!ser) { - ecs_strbuf_appendch(buf, '0'); - continue; - } +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; + } - void *ptr = ecs_vec_first(&table->data.columns[storage_column]); - if (array_to_json_buf_w_type_data(world, ptr, it->count, buf, comp, ser)) { - return -1; - } + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid with in assign_stmt"); + return NULL; } - flecs_json_array_pop(buf); + state->assembly_stmt = true; - return 0; + return ptr + 9; } 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, - const ecs_json_ser_idr_t *ser_idr) +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) { - 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); - } else { - if (!desc || desc->serialize_ids) { - flecs_json_serialize_iter_result_ids(world, it, buf); - } + 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; } - /* 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); + 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; } - /* Write variable values for current result */ - if (!desc || desc->serialize_variables) { - flecs_json_serialize_iter_result_variables(world, it, buf); - } + *type_out = prop_type; - /* Write labels for variables */ - if (desc && desc->serialize_variable_labels) { - flecs_json_serialize_iter_result_variable_labels(world, it, buf); + 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; } - /* Write ids for variables */ - if (desc && desc->serialize_variable_ids) { - flecs_json_serialize_iter_result_variable_ids(it, buf); + 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); } - /* 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); + if (ptr[0] != '=') { + ecs_parser_error(name, expr, ptr - expr, + "expected '=' after const declaration"); + return NULL; } - /* 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); + 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; } - /* Write ids for entities */ - if (desc && desc->serialize_entity_ids) { - flecs_json_serialize_iter_result_entity_ids(it, buf); + ptr = ecs_parse_ws(ptr); + + if (ptr[0] != ':') { + ecs_parser_error(name, expr, ptr - expr, + "expected ':' after prop declaration"); + return NULL; } - /* Write labels for entities */ - if (desc && desc->serialize_entity_labels) { - flecs_json_serialize_iter_result_entity_labels(it, buf, ser_idr); + ecs_entity_t prop_type; + ptr = plecs_parse_var_type(world, name, expr, ptr, state, &prop_type); + if (!ptr) { + return NULL; } - /* Write colors for entities */ - if (desc && desc->serialize_colors) { - flecs_json_serialize_iter_result_colors(it, buf, ser_idr); + 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; } - /* Serialize component values */ - if (desc && desc->serialize_table) { - if (flecs_json_serialize_iter_result_columns(world, it, buf)) { - return -1; - } - } else { - if (!desc || desc->serialize_values) { - if (flecs_json_serialize_iter_result_values(world, it, buf)) { - return -1; - } + 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 + }); } - /* 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); - } + if (ptr[0] != '=') { + ecs_parser_error(name, expr, ptr - expr, + "expected '=' after prop type"); + return NULL; } -#endif - flecs_json_object_pop(buf); + ecs_os_strcpy(state->var_name, prop_name); + state->last_assign_id = prop_type; + state->var_stmt = true; + state->var_is_prop = true; - return 0; + return plecs_parse_fluff(ptr + 1); } -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) +static +const char* plecs_parse_scope_open( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_time_t duration = {0}; - if (desc && desc->measure_eval_duration) { - ecs_time_measure(&duration); - } - - flecs_json_object_push(buf); + state->isa_stmt = false; - /* Serialize component ids of the terms (usually provided by query) */ - if (!desc || desc->serialize_term_ids) { - flecs_json_serialize_iter_ids(world, it, buf); + if (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid scope in assign_stmt"); + return NULL; } - /* Serialize type info if enabled */ - if (desc && desc->serialize_type_info) { - flecs_json_serialize_type_info(world, it, buf); - } + state->sp ++; - /* Serialize variable names, if iterator has any */ - flecs_json_serialize_iter_variables(it, buf); + ecs_entity_t scope = 0; + ecs_entity_t default_scope_type = 0; + bool assembly_stmt = false; - /* Serialize results */ - flecs_json_memberl(buf, "results"); - flecs_json_array_push(buf); + if (!state->with_stmt) { + if (state->last_subject) { + scope = state->last_subject; + ecs_set_scope(world, state->last_subject); - /* Use instancing for improved performance */ - ECS_BIT_SET(it->flags, EcsIterIsInstanced); + /* 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 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); - } + 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); + } + } - /* Cache id record for flecs.doc ids */ - ecs_json_ser_idr_t ser_idr = {NULL, NULL}; -#ifdef FLECS_DOC - ser_idr.idr_doc_name = flecs_id_record_get(world, - ecs_pair_t(EcsDocDescription, EcsName)); - ser_idr.idr_doc_color = flecs_id_record_get(world, - ecs_pair_t(EcsDocDescription, EcsDocColor)); -#endif + state->scope[state->sp] = scope; + state->default_scope_type[state->sp] = default_scope_type; - ecs_iter_next_action_t next = it->next; - while (next(it)) { - if (flecs_json_serialize_iter_result(world, it, buf, desc, &ser_idr)) { - ecs_strbuf_reset(buf); - ecs_iter_fini(it); - return -1; + 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; } - flecs_json_array_pop(buf); + state->using_frames[state->sp] = state->using_frame; + state->with_frames[state->sp] = state->with_frame; + state->with_stmt = false; - if (desc && desc->measure_eval_duration) { - double dt = ecs_time_measure(&duration); - flecs_json_memberl(buf, "eval_duration"); - flecs_json_number(buf, dt); - } + ecs_vars_push(&state->vars); - flecs_json_object_pop(buf); + /* 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 0; + return ptr; } -char* ecs_iter_to_json( - const ecs_world_t *world, - ecs_iter_t *it, - const ecs_iter_to_json_desc_t *desc) +static +void plecs_free_with_frame( + ecs_world_t *world, + plecs_state_t *state) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - - if (ecs_iter_to_json_buf(world, it, &buf, desc)) { - ecs_strbuf_reset(&buf); - return NULL; + 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; + } } +} - return ecs_strbuf_get(&buf); +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); + } } -int ecs_world_to_json_buf( +static +const char* plecs_parse_scope_close( ecs_world_t *world, - ecs_strbuf_t *buf_out, - const ecs_world_to_json_desc_t *desc) + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_filter_t f = ECS_FILTER_INIT; - ecs_filter_desc_t filter_desc = {0}; - filter_desc.storage = &f; + if (state->isa_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "invalid '}' after inheritance statement"); + return NULL; + } - 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 (state->assign_stmt) { + ecs_parser_error(name, expr, ptr - expr, + "unfinished assignment before }"); + return NULL; + } - 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; + 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; } } - if (ecs_filter_init(world, &filter_desc) == NULL) { - return -1; + 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_iter_t it = ecs_filter_iter(world, &f); - ecs_iter_to_json_desc_t json_desc = { - .serialize_table = true, - .serialize_entities = true - }; + ecs_id_t id = state->scope[state->sp]; + if (!id || ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_set_with(world, id); + } - int ret = ecs_iter_to_json_buf(world, &it, buf_out, &json_desc); - ecs_filter_fini(&f); - return ret; + 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); } -char* ecs_world_to_json( +static +const char *plecs_parse_plecs_term( ecs_world_t *world, - const ecs_world_to_json_desc_t *desc) + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_term_t term = {0}; + ecs_entity_t decl_id = 0; + if (state->decl_stmt) { + decl_id = state->last_predicate; + } - if (ecs_world_to_json_buf(world, &buf, desc)) { - ecs_strbuf_reset(&buf); + ptr = ecs_parse_term(world, name, expr, ptr, &term); + if (!ptr) { return NULL; } - return ecs_strbuf_get(&buf); -} + if (flecs_isident(ptr[0])) { + state->decl_type = true; + } -#endif + if (!ecs_term_is_initialized(&term)) { + ecs_parser_error(name, expr, ptr - expr, "expected identifier"); + return NULL; /* No term found */ + } -/** - * @file json/serialize_type_info.c - * @brief Serialize type (reflection) information to JSON. - */ + 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); + } -#ifdef FLECS_JSON + state->decl_type = false; -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; - default: - return -1; - } + ecs_term_fini(&term); - return 0; + return ptr; } static -void json_typeinfo_ser_constants( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) +const char* plecs_parse_annotation( + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - 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])); + do { + if(state->annot_count >= STACK_MAX_SIZE) { + ecs_parser_error(name, expr, ptr - expr, + "max number of annotations reached"); + return NULL; } - } -} -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); -} + char ch; + const char *start = ptr; + for (; (ch = *ptr) && ch != '\n'; ptr ++) { } -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); -} + 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'; -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\""); + state->annot[state->annot_count] = annot; + state->annot_count ++; - flecs_json_next(str); - if (json_typeinfo_ser_type(world, elem_type, str)) { - goto error; - } + ptr = plecs_parse_fluff(ptr); + } while (ptr[0] == '@'); - ecs_strbuf_list_append(str, "%u", count); - return 0; -error: - return -1; + return ptr; } static -int json_typeinfo_ser_array_type( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) +void plecs_clear_annotations( + plecs_state_t *state) { - 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; + int32_t i, count = state->annot_count; + for (i = 0; i < count; i ++) { + ecs_os_free(state->annot[i]); } - - return 0; -error: - return -1; + state->annot_count = 0; } static -int json_typeinfo_ser_vector( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) +const char* plecs_parse_stmt( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + plecs_state_t *state) { - const EcsVector *arr = ecs_get(world, type, EcsVector); - ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_strbuf_list_appendstr(str, "\"vector\""); + 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; - flecs_json_next(str); - if (json_typeinfo_ser_type(world, arr->type, str)) { - goto error; - } + plecs_clear_annotations(state); - return 0; -error: - return -1; -} + ptr = plecs_parse_fluff(ptr); -/* 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); + char ch = ptr[0]; - 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); - } + 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; } - return 0; -} - -/* 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) -{ - 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); +term_expr: + if (!ptr[0]) { + goto done; } - 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_abort(ECS_INTERNAL_ERROR, NULL); - break; - default: - if (json_typeinfo_ser_primitive( - flecs_json_op_to_primitive_kind(op->kind), str)) - { - /* Unknown operation */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - return -1; + 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; } - break; + } else if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { + goto error; } - ecs_entity_t unit = op->unit; - if (unit) { - flecs_json_next(str); - flecs_json_next(str); - - flecs_json_object_push(str); - json_typeinfo_ser_unit(world, str, unit); - flecs_json_object_pop(str); + 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; } - 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) -{ - 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); - } - } +next_term: + ptr = plecs_parse_fluff(ptr); - 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); - break; - case EcsOpPop: - flecs_json_object_pop(str); - break; - default: - if (json_typeinfo_ser_type_op(world, op, str)) { + 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] == '{') { + 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; } - break; + + goto next_term; + } else { + ptr = plecs_parse_fluff(ptr + 1); + goto scope_open; } } - 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; - } + state->assign_stmt = false; + goto done; - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - ecs_strbuf_appendch(buf, '0'); - return 0; - } +decl_stmt: + state->decl_stmt = true; + goto term_expr; - 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); +inherit_stmt: + ptr = plecs_parse_inherit_stmt(name, expr, ptr, state); + if (!ptr) goto error; - return json_typeinfo_ser_type_ops(world, ops, count, buf); -} + /* Expect base identifier */ + goto term_expr; -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); -} +assign_stmt: + ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); + if (!ptr) goto error; -char* ecs_type_info_to_json( - const ecs_world_t *world, - ecs_entity_t type) -{ - ecs_strbuf_t str = ECS_STRBUF_INIT; + ptr = plecs_parse_fluff(ptr); - if (ecs_type_info_to_json_buf(world, type, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; + /* Assignment without a preceding component */ + if (ptr[0] == '{') { + goto assign_expr; } - return ecs_strbuf_get(&str); -} - -#endif - -/** - * @file json/deserialize.c - * @brief Deserialize JSON strings into (component) values. - */ - -#include + /* Expect component identifiers */ + goto term_expr; -#ifdef FLECS_JSON +assign_expr: + ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, NULL); + if (!ptr) goto error; -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) { + 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; } - 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); + 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; +} - *out = result; +scope_open: + ptr = plecs_parse_scope_open(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; - return json; +scope_close: + ptr = plecs_parse_scope_close(world, name, expr, ptr, state); + if (!ptr) goto error; + goto done; + +done: + return ptr; 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) +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) { - 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; + const char *ptr = expr; + ecs_term_t term = {0}; + plecs_state_t state = {0}; - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); - if (cur.valid == false) { - return NULL; + if (!expr) { + return 0; } - if (desc) { - name = desc->name; - expr = desc->expr; - cur.lookup_action = desc->lookup_action; - cur.lookup_ctx = desc->lookup_ctx; + 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; } - 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; - } + ecs_vars_init(world, &state.vars); - token = ecs_strbuf_get(&large_token); - token_kind = JsonString; + 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 (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 (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 (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, json - expr, "expected '{'"); - return NULL; + if (instance) { + ecs_set_scope(world, instance); } - } else if (token_kind == JsonArrayClose) { - depth --; + } + } - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, json - expr, "expected '}'"); - return NULL; - } + if (vars) { + state.vars.root.parent = vars->cur; + } - 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 == 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 { + do { + expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); + if (!ptr) { goto error; } - if (token != token_buffer) { - ecs_os_free(token); - token = token_buffer; + if (!ptr[0]) { + break; /* End of expression */ } + } while (true); - if (!depth) { - break; - } + ecs_set_scope(world, prev_scope); + ecs_set_with(world, prev_with); + plecs_clear_annotations(&state); + + if (state.is_module) { + state.sp --; } - return json; + 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: - return NULL; + 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; } -const char* ecs_entity_from_json( +int ecs_plecs_from_str( ecs_world_t *world, - ecs_entity_t e, - const char *json, - const ecs_from_json_desc_t *desc_param) + const char *name, + const char *expr) { - ecs_json_token_t token_kind = 0; - char token[ECS_MAX_TOKEN_SIZE]; + return flecs_plecs_parse(world, name, expr, NULL, 0, 0); +} - ecs_from_json_desc_t desc = {0}; +static +char* flecs_load_from_file( + const char *filename) +{ + FILE* file; + char* content = NULL; + int32_t bytes; + size_t size; - const char *name = NULL, *expr = json, *ids = NULL, *values = NULL, *lah; - if (desc_param) { - desc = *desc_param; + /* Open file for reading */ + ecs_os_fopen(&file, filename, "r"); + if (!file) { + ecs_err("%s (%s)", ecs_os_strerror(errno), filename); + goto error; } - json = flecs_json_expect(json, JsonObjectOpen, token, &desc); - if (!json) { + /* Determine file size */ + fseek(file, 0 , SEEK_END); + bytes = (int32_t)ftell(file); + if (bytes == -1) { goto error; } + rewind(file); - lah = flecs_json_parse(json, &token_kind, token); - if (!lah) { + /* 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'; } - if (token_kind == JsonObjectClose) { - return lah; - } + fclose(file); - json = flecs_json_expect_member(json, token, &desc); - if (!json) { - return NULL; + 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; } - if (!ecs_os_strcmp(token, "path")) { - json = flecs_json_expect(json, JsonString, token, &desc); - if (!json) { - goto error; - } + int result = ecs_plecs_from_str(world, filename, script); + ecs_os_free(script); + return result; +} - ecs_add_fullpath(world, e, token); +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); + } +} - json = flecs_json_parse(json, &token_kind, token); - if (!json) { - goto error; - } +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)); +} - if (token_kind == JsonObjectClose) { - return json; - } else if (token_kind != JsonComma) { - ecs_parser_error(name, expr, json - expr, "unexpected character"); - goto error; - } +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); - 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; + 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); } - json = flecs_json_expect(json, JsonArrayOpen, token, &desc); - if (!json) { - goto error; + 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); } - ids = json; + 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); - json = flecs_json_skip_array(json, token, &desc); - if (!json) { - return NULL; + if (is_defer) { + flecs_resume_readonly(real_world, &srs); } - 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; - } + return result; +} - json = flecs_json_expect_member_name(json, token, "values", &desc); - if (!json) { - goto error; - } +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); - json = flecs_json_expect(json, JsonArrayOpen, token, &desc); - if (!json) { - goto error; + if (!e) { + if (desc->filename) { + e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", NULL); + } else { + e = ecs_new_id(world); } - - 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) { + script = desc->str; + if (!script && desc->filename) { + script = flecs_load_from_file(desc->filename); + if (!script) { 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; - } + if (ecs_script_update(world, e, 0, script, NULL)) { + goto error; + } - ids = flecs_json_parse(ids, &token_kind, token); - if (!ids) { - goto error; - } + if (script != desc->str) { + /* Safe cast, only happens when script is loaded from file */ + ecs_os_free(ECS_CONST_CAST(char*, script)); + } - if (token_kind == JsonComma) { - /* Id is a pair*/ - ids = flecs_json_parse_path(world, ids, token, &second, &desc); - if (!ids) { - goto error; - } + 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; +} - 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; - } +void FlecsScriptImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsScript); + ECS_IMPORT(world, FlecsMeta); - lah = flecs_json_parse(ids, &token_kind, token); - if (!lah) { - goto error; - } + ecs_set_name_prefix(world, "Ecs"); + ECS_COMPONENT_DEFINE(world, EcsScript); - 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; - } + ecs_set_hooks(world, EcsScript, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsScript), + .dtor = ecs_dtor(EcsScript) + }); - 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; - } + ecs_add_id(world, ecs_id(EcsScript), EcsTag); + ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); - /* 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; + 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 } } + }); +} - if (values) { - ecs_from_json_desc_t parse_desc = { - .name = name, - .expr = expr, - }; +#endif - values = ecs_ptr_from_json( - world, type_id, comp_ptr, values, &parse_desc); - if (!values) { - goto error; - } +/** + * @file addons/rest.c + * @brief Rest addon. + */ - 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); - } +#ifdef FLECS_REST - ecs_modified_id(world, e, id); - } - } while(ids[0]); +static ECS_TAG_DECLARE(EcsRestPlecs); - return flecs_json_expect(json, JsonObjectClose, token, &desc); -error: - return NULL; -} +typedef struct { + ecs_world_t *world; + ecs_http_server_t *srv; + int32_t rc; +} ecs_rest_ctx_t; -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); +/* Global statistics */ +int64_t ecs_rest_request_count = 0; +int64_t ecs_rest_entity_count = 0; +int64_t ecs_rest_entity_error_count = 0; +int64_t ecs_rest_query_count = 0; +int64_t ecs_rest_query_error_count = 0; +int64_t ecs_rest_query_name_count = 0; +int64_t ecs_rest_query_name_error_count = 0; +int64_t ecs_rest_query_name_from_cache_count = 0; +int64_t ecs_rest_enable_count = 0; +int64_t ecs_rest_enable_error_count = 0; +int64_t ecs_rest_delete_count = 0; +int64_t ecs_rest_delete_error_count = 0; +int64_t ecs_rest_world_stats_count = 0; +int64_t ecs_rest_pipeline_stats_count = 0; +int64_t ecs_rest_stats_error_count = 0; + +static ECS_COPY(EcsRest, dst, src, { + ecs_rest_ctx_t *impl = src->impl; + if (impl) { + impl->rc ++; } -} -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_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) { - ecs_entity_t scope = 0; - if (parent) { - scope = ecs_set_scope(world, parent); + (void)file; (void)line; + + if (level < 0) { + if (rest_prev_log) { + // Also log to previous log function + ecs_log_enable_colors(true); + rest_prev_log(level, file, line, msg); + ecs_log_enable_colors(false); + } } - ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); - if (parent) { - ecs_set_scope(world, scope); + + 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_json_mark_reserved( - ecs_map_t *anonymous_ids, - ecs_entity_t e) +void flecs_reply_verror( + ecs_http_reply_t *reply, + const char *fmt, + va_list args) { - ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); - ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); - reserved[0] = 0; + ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); + ecs_strbuf_vappend(&reply->body, fmt, args); + ecs_strbuf_appendlit(&reply->body, "\"}"); } static -bool flecs_json_name_is_anonymous( - const char *name) +void flecs_reply_error( + ecs_http_reply_t *reply, + const char *fmt, + ...) { - if (isdigit(name[0])) { - const char *ptr; - for (ptr = name + 1; *ptr; ptr ++) { - if (!isdigit(*ptr)) { - break; - } - } - if (!(*ptr)) { - return true; - } - } - return false; + va_list args; + va_start(args, fmt); + flecs_reply_verror(reply, fmt, args); + va_end(args); } static -ecs_entity_t flecs_json_ensure_entity( - ecs_world_t *world, +void flecs_rest_bool_param( + const ecs_http_request_t *req, const char *name, - ecs_map_t *anonymous_ids) + bool *value_out) { - 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 */ - } + const char *value = ecs_http_get_param(req, name); + if (value) { + if (!ecs_os_strcmp(value, "true")) { + value_out[0] = true; } 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]); - } + value_out[0] = false; } + } +} - 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); - } +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); } +} - return e; +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 -ecs_table_t* flecs_json_parse_table( +void flecs_rest_parse_json_ser_entity_params( ecs_world_t *world, - const char *json, - char *token, - const ecs_from_json_desc_t *desc) + ecs_entity_to_json_desc_t *desc, + const ecs_http_request_t *req) { - 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; - } + 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); - ecs_entity_t first = flecs_json_lookup(world, 0, token, desc); - if (!first) { - goto error; - } + char *rel = NULL; + flecs_rest_string_param(req, "refs", &rel); + if (rel) { + desc->serialize_refs = ecs_lookup_fullpath(world, rel); + } +} - json = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonComma) { - json = flecs_json_expect(json, JsonString, token, desc); - if (!json) { - goto error; - } +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, "table", &desc->serialize_table); +} - ecs_entity_t second = flecs_json_lookup(world, 0, token, desc); - if (!second) { - goto error; - } +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); - id = ecs_pair(first, second); + ecs_os_linc(&ecs_rest_entity_count); - 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; - } + 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; + ecs_os_linc(&ecs_rest_entity_error_count); + return true; + } - table = ecs_table_add_id(world, table, id); - if (!table) { - goto error; - } + 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; +} - 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]); +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; +} - return table; -error: - return NULL; +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 -int flecs_json_parse_entities( +bool flecs_rest_set( 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) + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) { - char name_token[ECS_MAX_TOKEN_SIZE]; - ecs_json_token_t token_kind = 0; - ecs_vec_clear(records); + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } - 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; - } + 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; +} - 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); +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))) { + ecs_os_linc(&ecs_rest_delete_error_count); + return true; + } - 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_delete(world, e); + + return true; +} - ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); - ecs_record_t** elem = ecs_vec_append_t(a, records, ecs_record_t*); - *elem = r; +static +bool flecs_rest_enable( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path, + bool enable) +{ + ecs_os_linc(&ecs_rest_enable_count); - 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]); + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + ecs_os_linc(&ecs_rest_enable_error_count); + return true; + } - return 0; -error: - return -1; + ecs_enable(world, e, enable); + + return true; } static -const char* flecs_json_parse_column( +bool flecs_rest_script( ecs_world_t *world, - ecs_table_t *table, - int32_t column, - const char *json, - char *token, - ecs_vec_t *records, - const ecs_from_json_desc_t *desc) + const ecs_http_request_t* req, + ecs_http_reply_t *reply) { - if (!table->storage_table) { - ecs_parser_error(desc->name, desc->expr, json - desc->expr, - "table has no components"); - goto error; + (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; } - if (column >= table->type.count) { - ecs_parser_error(desc->name, desc->expr, json - desc->expr, - "more value arrays than component columns in table"); - goto error; - } + bool prev_color = ecs_log_enable_colors(false); + rest_prev_log = ecs_os_api.log_; + ecs_os_api.log_ = flecs_rest_capture_log; - int32_t data_column = table->storage_map[column]; - 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]", - column, table_str); + ecs_entity_t script = ecs_script(world, { + .entity = ecs_entity(world, { .name = "scripts.main" }), + .str = data + }); - ecs_os_free(table_str); - goto error; + if (!script) { + char *err = flecs_rest_get_captured_log(); + char *escaped_err = ecs_astresc('"', err); + flecs_reply_error(reply, escaped_err); + ecs_os_linc(&ecs_rest_query_error_count); + reply->code = 400; /* bad request */ + ecs_os_free(escaped_err); + ecs_os_free(err); } - ecs_json_token_t token_kind = 0; - ecs_vec_t *data = &table->data.columns[data_column]; - ecs_type_info_t *ti = table->type_info[data_column]; - 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; + ecs_os_api.log_ = rest_prev_log; + ecs_log_enable_colors(prev_color); - do { - ecs_record_t *r = record_array[entity]; - int32_t row = ECS_RECORD_TO_ROW(r->row); - ecs_assert(ecs_vec_get_t( - &table->data.records, ecs_record_t*, row)[0] == r, - ECS_INTERNAL_ERROR, NULL); + return true; +#else + return false; +#endif +} - void *ptr = ecs_vec_get(data, size, row); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); +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); + reply->code = 400; + ecs_os_free(escaped_err); + ecs_os_free(err); + } +} - json = ecs_ptr_from_json(world, type, ptr, json, desc); - if (!json) { - break; - } +static +int 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 = {false}; + desc.serialize_entities = true; + desc.serialize_variables = true; + flecs_rest_parse_json_ser_iter_params(&desc, req); - 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 ']'"); - } + int32_t offset = 0; + int32_t limit = 1000; - entity ++; - } while (json[0]); + flecs_rest_int_param(req, "offset", &offset); + flecs_rest_int_param(req, "limit", &limit); - return json; -error: - return NULL; + if (offset < 0 || limit < 0) { + flecs_reply_error(reply, "invalid offset/limit parameter"); + reply->code = 400; + return -1; + } + + 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); + return -1; + } + + return 0; } static -const char* flecs_json_parse_values( +bool flecs_rest_reply_existing_query( 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) + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *name) { - ecs_allocator_t *a = &world->allocator; - ecs_json_token_t token_kind = 0; - int32_t column = 0; + ecs_os_linc(&ecs_rest_query_name_count); - ecs_vec_clear(columns_set); + ecs_entity_t q = ecs_lookup_fullpath(world, name); + if (!q) { + flecs_reply_error(reply, "unresolved identifier '%s'", name); + reply->code = 404; + ecs_os_linc(&ecs_rest_query_name_error_count); + return true; + } - do { - json = flecs_json_parse(json, &token_kind, token); - if (!json) { - goto error; + 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; + ecs_os_linc(&ecs_rest_query_name_error_count); + 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; + ecs_os_linc(&ecs_rest_query_name_error_count); + return true; + } + if (ecs_rule_parse_vars(poly->poly, &it, vars) == NULL) { + flecs_rest_reply_set_captured_log(reply); + ecs_os_linc(&ecs_rest_query_name_error_count); + return true; } + } - 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; - } + flecs_rest_iter_to_reply(world, req, reply, &it); - ecs_id_t *id_set = ecs_vec_append_t(a, columns_set, ecs_id_t); - *id_set = table->type.array[column]; + ecs_os_api.log_ = rest_prev_log; + ecs_log_enable_colors(prev_color); - 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; - } + 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); + } + + ecs_os_linc(&ecs_rest_query_count); + + const char *q = ecs_http_get_param(req, "q"); + if (!q) { + ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); + reply->code = 400; /* bad request */ + ecs_os_linc(&ecs_rest_query_error_count); + return true; + } + + 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); + ecs_os_linc(&ecs_rest_query_error_count); + } 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; +} + +#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_GAUGE_APPEND(reply, stats, tables.tag_only_count, "Tables with only tags"); + ECS_GAUGE_APPEND(reply, stats, tables.trivial_only_count, "Tables with only trivial types (no hooks)"); + ECS_GAUGE_APPEND(reply, stats, tables.record_count, "Table records registered with search indices"); + ECS_GAUGE_APPEND(reply, stats, tables.storage_count, "Component storages for all tables"); + 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, ids.count, "Component, tag and pair ids in use"); + ECS_GAUGE_APPEND(reply, stats, ids.tag_count, "Tag ids in use"); + ECS_GAUGE_APPEND(reply, stats, ids.component_count, "Component ids in use"); + ECS_GAUGE_APPEND(reply, stats, ids.pair_count, "Pair ids in use"); + ECS_GAUGE_APPEND(reply, stats, ids.wildcard_count, "Wildcard ids in use"); + ECS_GAUGE_APPEND(reply, stats, ids.type_count, "Registered component types"); + ECS_COUNTER_APPEND(reply, stats, ids.create_count, "Number of new component, tag and pair ids created"); + ECS_COUNTER_APPEND(reply, stats, ids.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, rest.request_count, "Received requests"); + ECS_COUNTER_APPEND(reply, stats, rest.entity_count, "Received entity/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.entity_error_count, "Failed entity/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.query_count, "Received query/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.query_error_count, "Failed query/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.query_name_count, "Received named query/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.query_name_error_count, "Failed named query/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.query_name_from_cache_count, "Named query/ requests from cache"); + ECS_COUNTER_APPEND(reply, stats, rest.enable_count, "Received enable/ and disable/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.enable_error_count, "Failed enable/ and disable/ requests"); + ECS_COUNTER_APPEND(reply, stats, rest.world_stats_count, "Received world stats requests"); + ECS_COUNTER_APPEND(reply, stats, rest.pipeline_stats_count, "Received pipeline stats requests"); + ECS_COUNTER_APPEND(reply, stats, rest.stats_error_count, "Failed stats requests"); + + 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 ++; } + } - 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; + 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; + ecs_os_linc(&ecs_rest_stats_error_count); + return false; } - } while (json[0]); + } - /* Send OnSet notifications */ - ecs_defer_begin(world); - ecs_type_t type = { - .array = columns_set->array, - .count = columns_set->count }; + if (!ecs_os_strcmp(category, "world")) { + const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, + EcsWorldStats, period); + flecs_world_stats_to_json(&reply->body, stats); + ecs_os_linc(&ecs_rest_world_stats_count); + return true; - int32_t table_count = ecs_table_count(table); - int32_t i, record_count = ecs_vec_count(records); + } 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); + ecs_os_linc(&ecs_rest_pipeline_stats_count); + return true; + + } else { + flecs_reply_error(reply, "bad request (unsupported category)"); + reply->code = 400; + ecs_os_linc(&ecs_rest_stats_error_count); + return false; + } + + return true; +} +#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); + used += table->data.records.count * ECS_SIZEOF(ecs_record_t*); + allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); + allocated += table->data.records.size * ECS_SIZEOF(ecs_record_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; + + ecs_os_linc(&ecs_rest_request_count); + + 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); + + /* 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 + }); + + 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 + + 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) +{ + if (!ecs_table_count(table)) { + 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 and records */ + ecs_allocator_t *a = &world->allocator; + result->entities = ecs_vec_copy_t(a, &main_data->entities, ecs_entity_t); + result->records = ecs_vec_copy_t(a, &main_data->records, ecs_record_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; + if (copy) { + ecs_vec_t dst = ecs_vec_copy(a, &column->data, size); + int32_t count = ecs_vec_count(&column->data); + void *dst_ptr = ecs_vec_first(&dst); + void *src_ptr = ecs_vec_first(&column->data); + + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(dst_ptr, count, ti); + } + + copy(dst_ptr, src_ptr, count, ti); + column->data = dst; + } else { + column->data = ecs_vec_copy(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->ids.count, t, world->info.id_count); + ECS_GAUGE_RECORD(&s->ids.tag_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->ids.component_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->ids.pair_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->ids.wildcard_count, t, world->info.wildcard_id_count); + ECS_GAUGE_RECORD(&s->ids.type_count, t, ecs_sparse_count(&world->type_info)); + ECS_COUNTER_RECORD(&s->ids.create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->ids.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_GAUGE_RECORD(&s->tables.tag_only_count, t, world->info.tag_table_count); + ECS_GAUGE_RECORD(&s->tables.trivial_only_count, t, world->info.trivial_table_count); + ECS_GAUGE_RECORD(&s->tables.storage_count, t, world->info.table_storage_count); + ECS_GAUGE_RECORD(&s->tables.record_count, t, world->info.table_record_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_REST + ECS_COUNTER_RECORD(&s->rest.request_count, t, ecs_rest_request_count); + ECS_COUNTER_RECORD(&s->rest.entity_count, t, ecs_rest_entity_count); + ECS_COUNTER_RECORD(&s->rest.entity_error_count, t, ecs_rest_entity_error_count); + ECS_COUNTER_RECORD(&s->rest.query_count, t, ecs_rest_query_count); + ECS_COUNTER_RECORD(&s->rest.query_error_count, t, ecs_rest_query_error_count); + ECS_COUNTER_RECORD(&s->rest.query_name_count, t, ecs_rest_query_name_count); + ECS_COUNTER_RECORD(&s->rest.query_name_error_count, t, ecs_rest_query_name_error_count); + ECS_COUNTER_RECORD(&s->rest.query_name_from_cache_count, t, ecs_rest_query_name_from_cache_count); + ECS_COUNTER_RECORD(&s->rest.enable_count, t, ecs_rest_enable_count); + ECS_COUNTER_RECORD(&s->rest.enable_error_count, t, ecs_rest_enable_error_count); + ECS_COUNTER_RECORD(&s->rest.world_stats_count, t, ecs_rest_world_stats_count); + ECS_COUNTER_RECORD(&s->rest.pipeline_stats_count, t, ecs_rest_pipeline_stats_count); + ECS_COUNTER_RECORD(&s->rest.stats_error_count, t, ecs_rest_stats_error_count); +#endif + +#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("id count", t, &s->ids.count); + flecs_gauge_print("tag id count", t, &s->ids.tag_count); + flecs_gauge_print("component id count", t, &s->ids.component_count); + flecs_gauge_print("pair id count", t, &s->ids.pair_count); + flecs_gauge_print("wildcard id count", t, &s->ids.wildcard_count); + flecs_gauge_print("type count", t, &s->ids.type_count); + flecs_counter_print("id create count", t, &s->ids.create_count); + flecs_counter_print("id delete count", t, &s->ids.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_gauge_print("tag table count", t, &s->tables.tag_only_count); + flecs_gauge_print("trivial table count", t, &s->tables.trivial_only_count); + flecs_gauge_print("table storage count", t, &s->tables.storage_count); + flecs_gauge_print("table cache record count", t, &s->tables.record_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); + + 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); + + 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 + }); - /* 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_set_scope(world, prev_scope); - ecs_defer_end(world); + /* DataRate units */ - return json; -error: - return NULL; -} + EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "DataRate" }); + prev_scope = ecs_set_scope(world, EcsDataRate); -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; + 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 + }); - json = flecs_json_expect(json, JsonObjectOpen, token, desc); - if (!json) { - goto error; - } + 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 + }); - json = flecs_json_expect_member_name(json, token, "ids", desc); - if (!json) { - goto error; - } + 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 + }); - json = flecs_json_expect(json, JsonArrayOpen, token, desc); - if (!json) { - goto error; - } + 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 + }); - ids = json; /* store start of ids array */ + 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 + }); - json = flecs_json_skip_array(json, token, desc); - if (!json) { - goto error; - } + 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 + }); - json = flecs_json_expect(json, JsonComma, token, desc); - if (!json) { - goto error; - } + 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 + }); - json = flecs_json_expect_member(json, token, desc); - if (!json) { - goto error; - } + 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_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); + ecs_set_scope(world, prev_scope); - json = flecs_json_expect(json, JsonComma, token, desc); - if (!json) { - goto error; - } + /* Percentage */ - json = flecs_json_expect_member(json, token, desc); - if (!json) { - goto error; - } - } + 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 + }); - if (ecs_os_strcmp(token, "entities")) { - ecs_parser_error(desc->name, desc->expr, json - desc->expr, - "expected 'entities'"); - goto error; - } + /* Angles */ - json = flecs_json_expect(json, JsonArrayOpen, token, desc); - if (!json) { - goto error; - } + 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 + }); - entities = json; /* store start of entity id array */ + 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); - json = flecs_json_skip_array(json, token, desc); - if (!json) { - goto error; - } + /* DeciBel */ - 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; - } + 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 + }); - json = flecs_json_expect(json, JsonArrayOpen, token, desc); - if (!json) { - goto error; - } + EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Frequency" }); + prev_scope = ecs_set_scope(world, EcsFrequency); - 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; - } + 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 + }); - /* Find table from ids */ - ecs_table_t *table = flecs_json_parse_table(world, ids, token, desc); - if (!table) { - goto error; - } + 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 + }); - /* Add entities to table */ - if (flecs_json_parse_entities(world, a, table, parent, - entities, token, records, desc)) - { - goto error; - } + 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 + }); - /* Parse values */ - if (values) { - json = flecs_json_parse_values(world, table, values, token, - records, columns_set, desc); - if (!json) { - goto error; - } + 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); - json = flecs_json_expect(json, JsonObjectClose, token, desc); - if (!json) { - goto error; - } - } + EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Uri" }); + prev_scope = ecs_set_scope(world, EcsUri); - return json; -error: - return NULL; -} + 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 + }); -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]; + 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 + }); - 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); + 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); - const char *name = NULL, *expr = json, *lah; - if (desc_arg) { - desc = *desc_arg; - } + /* Documentation */ +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); - 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; - } + 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"); - json = flecs_json_expect(json, JsonObjectOpen, token, &desc); - if (!json) { - goto error; - } + 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"); - json = flecs_json_expect_member_name(json, token, "results", &desc); - if (!json) { - goto error; - } + ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); - json = flecs_json_expect(json, JsonArrayOpen, token, &desc); - if (!json) { - goto error; - } + ecs_doc_set_brief(world, EcsElectricCurrent, + "Units of electrical current (e.g. \"2 ampere\")"); - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonArrayClose) { - json = lah; - goto end; - } + ecs_doc_set_brief(world, EcsAmount, + "Units of amount of substance (e.g. \"2 mole\")"); - do { - json = flecs_json_parse_result(world, a, json, token, - &records, &columns_set, &desc); - if (!json) { - goto error; - } + ecs_doc_set_brief(world, EcsLuminousIntensity, + "Units of luminous intensity (e.g. \"1 candela\")"); - 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]); + ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); -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); + ecs_doc_set_brief(world, EcsLength, + "Units of length (e.g. \"5 meters\", \"20 miles\")"); - json = flecs_json_expect(json, JsonObjectClose, token, &desc); - if (!json) { - goto error; - } + ecs_doc_set_brief(world, EcsPressure, + "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); - 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); + ecs_doc_set_brief(world, EcsSpeed, + "Units of movement (e.g. \"5 meters/second\")"); - return NULL; -} + ecs_doc_set_brief(world, EcsAcceleration, + "Unit of speed increase (e.g. \"5 meters/second/second\")"); -#endif + ecs_doc_set_brief(world, EcsTemperature, + "Units of temperature (e.g. \"5 degrees Celsius\")"); -/** - * @file addons/rest.c - * @brief Rest addon. - */ + 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\")"); -#ifdef FLECS_REST + ecs_doc_set_brief(world, EcsAngle, + "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); -ECS_TAG_DECLARE(EcsRestPlecs); + ecs_doc_set_brief(world, EcsFrequency, + "The number of occurrences of a repeating event per unit of time."); -typedef struct { - ecs_world_t *world; - ecs_http_server_t *srv; - int32_t rc; -} ecs_rest_ctx_t; + ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); +#endif +} -/* Global statistics */ -int64_t ecs_rest_request_count = 0; -int64_t ecs_rest_entity_count = 0; -int64_t ecs_rest_entity_error_count = 0; -int64_t ecs_rest_query_count = 0; -int64_t ecs_rest_query_error_count = 0; -int64_t ecs_rest_query_name_count = 0; -int64_t ecs_rest_query_name_error_count = 0; -int64_t ecs_rest_query_name_from_cache_count = 0; -int64_t ecs_rest_enable_count = 0; -int64_t ecs_rest_enable_error_count = 0; -int64_t ecs_rest_set_count = 0; -int64_t ecs_rest_set_error_count = 0; -int64_t ecs_rest_delete_count = 0; -int64_t ecs_rest_delete_error_count = 0; -int64_t ecs_rest_world_stats_count = 0; -int64_t ecs_rest_pipeline_stats_count = 0; -int64_t ecs_rest_stats_error_count = 0; +#endif -static ECS_COPY(EcsRest, dst, src, { - ecs_rest_ctx_t *impl = src->impl; - if (impl) { - impl->rc ++; - } +/** + * @file datastructures/allocator.c + * @brief Allocator for any size. + * + * Allocators create a block allocator for each requested size. + */ - 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_size_t flecs_allocator_size( + ecs_size_t size) +{ + return ECS_ALIGN(size, 16); +} -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 +ecs_size_t flecs_allocator_size_hash( + ecs_size_t size) +{ + return size >> 4; +} -static char *rest_last_err; -static ecs_os_api_log_t rest_prev_log; +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); +} -static -void flecs_rest_capture_log( - int32_t level, - const char *file, - int32_t line, - const char *msg) +void flecs_allocator_fini( + ecs_allocator_t *a) { - (void)file; (void)line; + 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); +} - if (level < 0) { - if (rest_prev_log) { - // Also log to previous log function - ecs_log_enable_colors(true); - rest_prev_log(level, file, line, msg); - ecs_log_enable_colors(false); - } +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; } - if (!rest_last_err && level < 0) { - rest_last_err = ecs_os_strdup(msg); + 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); } -} -static -char* flecs_rest_get_captured_log(void) { - char *result = rest_last_err; - rest_last_err = NULL; + ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); + return result; } -static -void flecs_reply_verror( - ecs_http_reply_t *reply, - const char *fmt, - va_list args) +char* flecs_strdup( + ecs_allocator_t *a, + const char* str) { - ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); - ecs_strbuf_vappend(&reply->body, fmt, args); - ecs_strbuf_appendlit(&reply->body, "\"}"); + 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; } -static -void flecs_reply_error( - ecs_http_reply_t *reply, - const char *fmt, - ...) +void flecs_strfree( + ecs_allocator_t *a, + char* str) { - va_list args; - va_start(args, fmt); - flecs_reply_verror(reply, fmt, args); - va_end(args); + ecs_size_t len = ecs_os_strlen(str); + flecs_free_n(a, char, len + 1, str); } -static -void flecs_rest_bool_param( - const ecs_http_request_t *req, - const char *name, - bool *value_out) +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src) { - 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; - } + 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 flecs_rest_int_param( - const ecs_http_request_t *req, - const char *name, - int32_t *value_out) +void ensure( + ecs_bitset_t *bs, + ecs_size_t size) { - const char *value = ecs_http_get_param(req, name); - if (value) { - *value_out = atoi(value); + 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); } } -static -void flecs_rest_string_param( - const ecs_http_request_t *req, - const char *name, - char **value_out) +void flecs_bitset_init( + ecs_bitset_t* bs) { - const char *value = ecs_http_get_param(req, name); - if (value) { - *value_out = (char*)value; - } + bs->size = 0; + bs->count = 0; + bs->data = NULL; } -static -void flecs_rest_parse_json_ser_entity_params( - ecs_entity_to_json_desc_t *desc, - const ecs_http_request_t *req) +void flecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count) { - 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, "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, "alerts", &desc->serialize_alerts); + if (count > bs->count) { + bs->count = count; + ensure(bs, count); + } } -static -void flecs_rest_parse_json_ser_iter_params( - ecs_iter_to_json_desc_t *desc, - const ecs_http_request_t *req) +void flecs_bitset_fini( + ecs_bitset_t *bs) { - flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); - flecs_rest_bool_param(req, "ids", &desc->serialize_ids); - 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, "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, "serialize_table", &desc->serialize_table); + ecs_os_free(bs->data); + bs->data = NULL; + bs->count = 0; } -static -bool flecs_rest_reply_entity( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +void flecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count) { - char *path = &req->path[7]; - ecs_dbg_2("rest: request entity '%s'", path); + int32_t elem = bs->count += count; + ensure(bs, elem); +} - ecs_os_linc(&ecs_rest_entity_count); +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; +} - 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; - ecs_os_linc(&ecs_rest_entity_error_count); - return true; - } +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; +} - ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; - flecs_rest_parse_json_ser_entity_params(&desc, req); - ecs_entity_to_json_buf(world, e, &reply->body, &desc); - return true; +int32_t flecs_bitset_count( + const ecs_bitset_t *bs) +{ + return bs->count; } -static -bool flecs_rest_reply_world( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +void flecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem) { - (void)req; - ecs_world_to_json_buf(world, &reply->body, NULL); - return true; + 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; } -static -ecs_entity_t flecs_rest_entity_from_path( - ecs_world_t *world, - ecs_http_reply_t *reply, - const char *path) +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) { - 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; + 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 -bool flecs_rest_set( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply, - const char *path) +ecs_block_allocator_chunk_header_t* flecs_balloc_block( + ecs_block_allocator_t *allocator) { - ecs_os_linc(&ecs_rest_set_count); + if (!allocator->chunk_size) { + return NULL; + } - ecs_entity_t e; - if (!(e = flecs_rest_entity_from_path(world, reply, path))) { - ecs_os_linc(&ecs_rest_set_error_count); - return true; + 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; } - 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; - ecs_os_linc(&ecs_rest_set_error_count); - return true; + 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; } - - return true; + + ecs_os_linc(&ecs_block_allocator_alloc_count); + + chunk->next = NULL; + return first_chunk; } -static -bool flecs_rest_delete( - ecs_world_t *world, - ecs_http_reply_t *reply, - const char *path) +void flecs_ballocator_init( + ecs_block_allocator_t *ba, + ecs_size_t size) { - ecs_os_linc(&ecs_rest_set_count); + 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_entity_t e; - if (!(e = flecs_rest_entity_from_path(world, reply, path))) { - ecs_os_linc(&ecs_rest_delete_error_count); - return true; +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; +} - ecs_delete(world, e); - - return true; +void flecs_ballocator_free( + ecs_block_allocator_t *ba) +{ + flecs_ballocator_fini(ba); + ecs_os_free(ba); } -static -bool flecs_rest_enable( - ecs_world_t *world, - ecs_http_reply_t *reply, - const char *path, - bool enable) +void* flecs_balloc( + ecs_block_allocator_t *ba) { - ecs_os_linc(&ecs_rest_enable_count); + void *result; +#ifdef FLECS_USE_OS_ALLOC + result = ecs_os_malloc(ba->data_size); +#else - ecs_entity_t e; - if (!(e = flecs_rest_entity_from_path(world, reply, path))) { - ecs_os_linc(&ecs_rest_enable_error_count); - return true; + if (!ba) return NULL; + + if (!ba->head) { + ba->head = flecs_balloc_block(ba); } - ecs_enable(world, e, enable); - - return true; + 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; } -static -bool flecs_rest_script( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +void* flecs_bcalloc( + ecs_block_allocator_t *ba) { - (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; - } +#ifdef FLECS_USE_OS_ALLOC + return ecs_os_calloc(ba->data_size); +#endif - bool prev_color = ecs_log_enable_colors(false); - rest_prev_log = ecs_os_api.log_; - ecs_os_api.log_ = flecs_rest_capture_log; + if (!ba) return NULL; + void *result = flecs_balloc(ba); + ecs_os_memset(result, 0, ba->data_size); + return result; +} - ecs_entity_t script = ecs_script(world, { - .entity = ecs_entity(world, { .name = "scripts.main" }), - .str = data - }); +void flecs_bfree( + ecs_block_allocator_t *ba, + void *memory) +{ +#ifdef FLECS_USE_OS_ALLOC + ecs_os_free(memory); + return; +#endif - if (!script) { - char *err = flecs_rest_get_captured_log(); - char *escaped_err = ecs_astresc('"', err); - flecs_reply_error(reply, escaped_err); - ecs_os_linc(&ecs_rest_query_error_count); - reply->code = 400; /* bad request */ - ecs_os_free(escaped_err); - ecs_os_free(err); + 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); } - ecs_os_api.log_ = rest_prev_log; - ecs_log_enable_colors(prev_color); - - return true; -#else - return false; + ba->alloc_count --; #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); - reply->code = 400; - ecs_os_free(escaped_err); - ecs_os_free(err); - } + 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"); } -static -int flecs_rest_iter_to_reply( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply, - ecs_iter_t *it) +void* flecs_brealloc( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory) { - ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; - 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"); - reply->code = 400; - return -1; + void *result; +#ifdef FLECS_USE_OS_ALLOC + result = ecs_os_realloc(memory, dst->data_size); +#else + if (dst == src) { + return memory; } - 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); - return -1; + 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 0; + return result; } -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) +void* flecs_bdup( + ecs_block_allocator_t *ba, + void *memory) { - ecs_os_linc(&ecs_rest_query_name_count); - - ecs_entity_t q = ecs_lookup_fullpath(world, name); - if (!q) { - flecs_reply_error(reply, "unresolved identifier '%s'", name); - reply->code = 404; - ecs_os_linc(&ecs_rest_query_name_error_count); - return true; +#ifdef FLECS_USE_OS_ALLOC + if (memory && ba->chunk_size) { + return ecs_os_memdup(memory, ba->data_size); + } else { + return NULL; } +#endif - 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; - ecs_os_linc(&ecs_rest_query_name_error_count); - return true; + void *result = flecs_balloc(ba); + if (result) { + ecs_os_memcpy(result, memory, ba->data_size); } + return result; +} - ecs_iter_t it; - ecs_iter_poly(world, poly->poly, &it, NULL); +// 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 - 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; +/* quick example: + string s="fjsakfdsjkf"; + uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); +*/ - 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; - ecs_os_linc(&ecs_rest_query_name_error_count); - return true; - } - if (ecs_rule_parse_vars(poly->poly, &it, vars) == NULL) { - flecs_rest_reply_set_captured_log(reply); - ecs_os_linc(&ecs_rest_query_name_error_count); - return true; - } - } - flecs_rest_iter_to_reply(world, req, reply, &it); +#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 - ecs_os_api.log_ = rest_prev_log; - ecs_log_enable_colors(prev_color); +#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 - return true; -} +//includes +#include +#include +#if defined(_MSC_VER) && defined(_M_X64) + #include + #pragma intrinsic(_umul128) +#endif -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); - } +//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 - ecs_os_linc(&ecs_rest_query_count); +//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 +} - const char *q = ecs_http_get_param(req, "q"); - if (!q) { - ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); - reply->code = 400; /* bad request */ - ecs_os_linc(&ecs_rest_query_error_count); - return true; - } +//multiply and xor mix function, aka MUM +static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } - 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; +//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 - ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ - .expr = q - }); - if (!r) { - flecs_rest_reply_set_captured_log(reply); - ecs_os_linc(&ecs_rest_query_error_count); - } else { - ecs_iter_t it = ecs_rule_iter(world, r); - flecs_rest_iter_to_reply(world, req, reply, &it); - ecs_rule_fini(r); +//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]); +} - ecs_os_api.log_ = rest_prev_log; - ecs_log_enable_colors(prev_color); +//the default secret parameters +static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; - return true; +uint64_t flecs_hash( + const void *data, + ecs_size_t length) +{ + return wyhash(data, flecs_ito(size_t, length), 0, wyp_); } -#ifdef FLECS_MONITOR +/** + * @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 -void _flecs_rest_array_append( - ecs_strbuf_t *reply, - const char *field, - int32_t field_len, - const ecs_float_t *values, - int32_t t) +int32_t flecs_hashmap_find_key( + const ecs_hashmap_t *map, + ecs_vec_t *keys, + ecs_size_t key_size, + const void *key) { - 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], '"'); + 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; + } } - - ecs_strbuf_list_pop(reply, "]"); + return -1; } -#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) +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) { - ecs_strbuf_list_appendch(reply, '"'); - ecs_strbuf_appendstrn(reply, field, field_len); - ecs_strbuf_appendlit(reply, "\":"); - ecs_strbuf_list_push(reply, "{", ","); + 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); +} - 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); +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); - if (brief) { - ecs_strbuf_list_appendlit(reply, "\"brief\":\""); - ecs_strbuf_appendstrn(reply, brief, brief_len); - ecs_strbuf_appendch(reply, '"'); + 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 } - ecs_strbuf_list_pop(reply, "}"); + flecs_ballocator_fini(&map->bucket_allocator); + ecs_map_fini(&map->impl); } -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) +void flecs_hashmap_copy( + ecs_hashmap_t *dst, + const ecs_hashmap_t *src) { - 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) + ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); -#define ECS_GAUGE_APPEND(reply, s, field, brief)\ - ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) + flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, + src->compare, src->impl.allocator); + ecs_map_copy(&dst->impl, &src->impl); -#define ECS_COUNTER_APPEND(reply, s, field, brief)\ - ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) + 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); + } +} -static -void flecs_world_stats_to_json( - ecs_strbuf_t *reply, - const EcsWorldStats *monitor_stats) +void* flecs_hashmap_get_( + const ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) { - 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_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); - 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"); + 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; + } - 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"); + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return NULL; + } - 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_GAUGE_APPEND(reply, stats, tables.tag_only_count, "Tables with only tags"); - ECS_GAUGE_APPEND(reply, stats, tables.trivial_only_count, "Tables with only trivial types (no hooks)"); - ECS_GAUGE_APPEND(reply, stats, tables.record_count, "Table records registered with search indices"); - ECS_GAUGE_APPEND(reply, stats, tables.storage_count, "Component storages for all tables"); - 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"); + return ecs_vec_get(&bucket->values, value_size, index); +} - ECS_GAUGE_APPEND(reply, stats, ids.count, "Component, tag and pair ids in use"); - ECS_GAUGE_APPEND(reply, stats, ids.tag_count, "Tag ids in use"); - ECS_GAUGE_APPEND(reply, stats, ids.component_count, "Component ids in use"); - ECS_GAUGE_APPEND(reply, stats, ids.pair_count, "Pair ids in use"); - ECS_GAUGE_APPEND(reply, stats, ids.wildcard_count, "Wildcard ids in use"); - ECS_GAUGE_APPEND(reply, stats, ids.type_count, "Registered component types"); - ECS_COUNTER_APPEND(reply, stats, ids.create_count, "Number of new component, tag and pair ids created"); - ECS_COUNTER_APPEND(reply, stats, ids.delete_count, "Number of component, pair and tag ids deleted"); +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); - 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"); + 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_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_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); + } + } - ECS_COUNTER_APPEND(reply, stats, rest.request_count, "Received requests"); - ECS_COUNTER_APPEND(reply, stats, rest.entity_count, "Received entity/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.entity_error_count, "Failed entity/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.query_count, "Received query/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.query_error_count, "Failed query/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.query_name_count, "Received named query/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.query_name_error_count, "Failed named query/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.query_name_from_cache_count, "Named query/ requests from cache"); - ECS_COUNTER_APPEND(reply, stats, rest.enable_count, "Received enable/ and disable/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.enable_error_count, "Failed enable/ and disable/ requests"); - ECS_COUNTER_APPEND(reply, stats, rest.world_stats_count, "Received world stats requests"); - ECS_COUNTER_APPEND(reply, stats, rest.pipeline_stats_count, "Received pipeline stats requests"); - ECS_COUNTER_APPEND(reply, stats, rest.stats_error_count, "Failed stats requests"); + return (flecs_hashmap_result_t){ + .key = key_ptr, .value = value_ptr, .hash = hash + }; +} - 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)"); +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_strbuf_list_pop(reply, "}"); +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); } -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) +void flecs_hm_bucket_remove( + ecs_hashmap_t *map, + ecs_hm_bucket_t *bucket, + uint64_t hash, + int32_t index) { - 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, '"'); + ecs_vec_remove(&bucket->keys, map->key_size, index); + ecs_vec_remove(&bucket->values, map->value_size, index); - if (!stats->task) { - ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); - ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); + 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); } - - 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) +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_strbuf_list_push(reply, "[", ","); + 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; - int32_t i, count = ecs_vec_count(&stats->stats.systems); - 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); + ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, + ecs_hm_bucket_t, hash); + if (!bucket) { + return; + } - 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_strbuf_list_pop(reply, "}"); - } + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return; } - ecs_strbuf_list_pop(reply, "]"); + flecs_hm_bucket_remove(map, bucket, hash, index); } -static -bool flecs_rest_reply_stats( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +void flecs_hashmap_remove_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) { - char *period_str = NULL; - flecs_rest_string_param(req, "period", &period_str); - char *category = &req->path[6]; + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); - 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; - ecs_os_linc(&ecs_rest_stats_error_count); - return false; + 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 (!ecs_os_strcmp(category, "world")) { - const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, - EcsWorldStats, period); - flecs_world_stats_to_json(&reply->body, stats); - ecs_os_linc(&ecs_rest_world_stats_count); - return true; + if (key_out) { + *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); + } + + return ecs_vec_get(&bucket->values, value_size, index); +} - } 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); - ecs_os_linc(&ecs_rest_pipeline_stats_count); - return true; +/** + * @file datastructures/map.c + * @brief Map data structure. + * + * Map data structure for 64bit keys and dynamic payload size. + */ - } else { - flecs_reply_error(reply, "bad request (unsupported category)"); - reply->code = 400; - ecs_os_linc(&ecs_rest_stats_error_count); - return false; - } - return true; +/* 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]; } -#else + +/* Get bucket count for number of elements */ static -bool flecs_rest_reply_stats( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +int32_t flecs_map_get_bucket_count( + int32_t count) { - (void)world; - (void)req; - (void)reply; - return false; + return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); } -#endif +/* Get bucket shift amount for a given bucket count */ static -void flecs_rest_reply_table_append_type( - ecs_world_t *world, - ecs_strbuf_t *reply, - const ecs_table_t *table) +uint8_t flecs_map_get_bucket_shift ( + int32_t bucket_count) { - 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, "]"); + return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); } +/* Get bucket index for provided map key */ static -void flecs_rest_reply_table_append_memory( - ecs_strbuf_t *reply, - const ecs_table_t *table) +int32_t flecs_map_get_bucket_index( + uint16_t bucket_shift, + ecs_map_key_t key) { - int32_t used = 0, allocated = 0; + ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); + return (int32_t)((11400714819323198485ull * key) >> bucket_shift); +} - used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); - used += table->data.records.count * ECS_SIZEOF(ecs_record_t*); - allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); - allocated += table->data.records.size * ECS_SIZEOF(ecs_record_t*); +/* 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]; +} - int32_t i, storage_count = table->storage_count; - ecs_type_info_t **ti = table->type_info; - ecs_vec_t *storages = table->data.columns; +/* 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; +} - for (i = 0; i < storage_count; i ++) { - used += storages[i].count * ti[i]->size; - allocated += storages[i].size * ti[i]->size; +/* 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; +} - 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, "}"); +/* 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 -void flecs_rest_reply_table_append( - ecs_world_t *world, - ecs_strbuf_t *reply, - const ecs_table_t *table) +ecs_map_val_t* flecs_map_bucket_get( + ecs_bucket_t *bucket, + ecs_map_key_t key) { - 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_append(reply, "\"refcount\":%d", table->_->refcount); - ecs_strbuf_list_pop(reply, "}"); + 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 -bool flecs_rest_reply_tables( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) +void flecs_map_rehash( + ecs_map_t *map, + int32_t count) { - (void)req; + 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); - 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); + 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); } - ecs_strbuf_list_pop(&reply->body, "]"); + map->bucket_count = count; + map->bucket_shift = flecs_map_get_bucket_shift(count); - return true; + /* 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); + } } -static -bool flecs_rest_reply( - const ecs_http_request_t* req, - ecs_http_reply_t *reply, - void *ctx) +void ecs_map_params_init( + ecs_map_params_t *params, + ecs_allocator_t *allocator) { - ecs_rest_ctx_t *impl = ctx; - ecs_world_t *world = impl->world; + params->allocator = allocator; + flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); +} - ecs_os_linc(&ecs_rest_request_count); +void ecs_map_params_fini( + ecs_map_params_t *params) +{ + flecs_ballocator_fini(¶ms->entry_allocator); +} - if (req->path == NULL) { - ecs_dbg("rest: bad request (missing path)"); - flecs_reply_error(reply, "bad request (missing path)"); - reply->code = 400; - return false; - } +void ecs_map_init_w_params( + ecs_map_t *result, + ecs_map_params_t *params) +{ + ecs_os_zeromem(result); - if (req->method == EcsHttpGet) { - /* Entity endpoint */ - if (!ecs_os_strncmp(req->path, "entity/", 7)) { - return flecs_rest_reply_entity(world, req, reply); + result->allocator = params->allocator; - /* Query endpoint */ - } else if (!ecs_os_strcmp(req->path, "query")) { - return flecs_rest_reply_query(world, req, reply); + 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); + } - /* World endpoint */ - } else if (!ecs_os_strcmp(req->path, "world")) { - return flecs_rest_reply_world(world, req, reply); + flecs_map_rehash(result, 0); +} - /* Stats endpoint */ - } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { - return flecs_rest_reply_stats(world, req, reply); +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); + } +} - /* Tables endpoint */ - } else if (!ecs_os_strncmp(req->path, "tables", 6)) { - return flecs_rest_reply_tables(world, req, reply); - } +void ecs_map_init( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + ecs_map_init_w_params(result, &(ecs_map_params_t) { + .allocator = allocator + }); +} - } 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]); +void ecs_map_init_if( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + if (!ecs_map_is_init(result)) { + ecs_map_init(result, allocator); + } +} - /* Enable endpoint */ - } else if (!ecs_os_strncmp(req->path, "enable/", 7)) { - return flecs_rest_enable(world, reply, &req->path[7], true); +void ecs_map_fini( + ecs_map_t *map) +{ + if (!ecs_map_is_init(map)) { + return; + } - /* Disable endpoint */ - } else if (!ecs_os_strncmp(req->path, "disable/", 8)) { - return flecs_rest_enable(world, reply, &req->path[8], false); + bool sanitize = false; +#ifdef FLECS_SANITIZE + sanitize = true; +#endif - /* Script endpoint */ - } else if (!ecs_os_strncmp(req->path, "script", 6)) { - return flecs_rest_script(world, req, reply); + /* 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 ++; } } - return false; + 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_http_server_t* ecs_rest_server_init( - ecs_world_t *world, - const ecs_http_server_desc_t *desc) +ecs_map_val_t* ecs_map_get( + const ecs_map_t *map, + ecs_map_key_t key) { - 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; + 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]; } - private_desc.callback = flecs_rest_reply; - private_desc.ctx = srv_ctx; + return NULL; +} - ecs_http_server_t *srv = ecs_http_server_init(&private_desc); - if (!srv) { - ecs_os_free(srv_ctx); - 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); } - srv_ctx->world = world; - srv_ctx->srv = srv; - srv_ctx->rc = 1; - srv_ctx->srv = srv; - return srv; + ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); + flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; } -void ecs_rest_server_fini( - ecs_http_server_t *srv) +void* ecs_map_insert_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) { - ecs_rest_ctx_t *srv_ctx = ecs_http_server_ctx(srv); - ecs_os_free(srv_ctx); - ecs_http_server_fini(srv); + void *elem = ecs_os_calloc(elem_size); + ecs_map_insert_ptr(map, key, (uintptr_t)elem); + return elem; } -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 - }); +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; + } - 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; - } + 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); + } - rest[i].impl = ecs_http_server_ctx(srv); + ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); + *v = 0; + return v; +} - ecs_http_server_start(srv); +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; } } -static -void DequeueRest(ecs_iter_t *it) { - EcsRest *rest = ecs_field(it, EcsRest, 1); +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); +} - 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); +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); } +} - 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); - } - } +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); } -static -void DisableRest(ecs_iter_t *it) { - ecs_world_t *world = it->world; +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 }; + } +} - ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){ - .id = ecs_id(EcsRest), - .src.flags = EcsSelf - }); +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; + } - 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); + 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; } } - } 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); - } + 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; } -} - -void FlecsRestImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsRest); - - ECS_IMPORT(world, FlecsPipeline); -#ifdef FLECS_PLECS - ECS_IMPORT(world, FlecsScript); -#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_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); + iter->entry = entry->next; + iter->res = &entry->key; - ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); + return true; +} - ecs_system(world, { - .entity = ecs_id(DequeueRest), - .no_readonly = 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_observer(world, { - .filter = { - .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = DisableRest - }); + ecs_map_init(dst, src->allocator); - ecs_set_name_prefix(world, "EcsRest"); - ECS_TAG_DEFINE(world, EcsRestPlecs); + 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)); + } } -#endif - /** - * @file addons/coredoc.c - * @brief Core doc addon. + * @file datastructures/name_index.c + * @brief Data structure for resolving 64bit keys by string (name). */ -#ifdef FLECS_COREDOC - -#define URL_ROOT "https://www.flecs.dev/flecs/md_docs_Relationships.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 = (char*)"size", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"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, "Flecs module with builtin components"); - - ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); - - ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all 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, ecs_id(EcsIdentifier), "Component used for entity names"); - ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); - ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); - - ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property"); - ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property"); - ecs_doc_set_brief(world, EcsFinal, "Final relationship property"); - ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property"); - ecs_doc_set_brief(world, EcsTag, "Tag relationship property"); - ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property"); - ecs_doc_set_brief(world, EcsTraversable, "Traversable relationship property"); - ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property"); - ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property"); - ecs_doc_set_brief(world, EcsWith, "With relationship property"); - ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property"); - ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property"); - ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); - ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property"); - ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property"); - ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property"); - ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship"); - ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship"); - ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship"); - ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); - ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); - ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); - ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); - - ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); - ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); - ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); - ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); - ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); - ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); - ecs_doc_set_link(world, EcsTraversable, URL_ROOT "#traversable-property"); - ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); - ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); - ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); - ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship"); - ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship"); - - /* 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"); +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; +} - 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"); +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); + } - 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"); + return ecs_os_memcmp(str1->value, str2->value, len1); +} - /* 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"); +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); +} - 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"); +void flecs_name_index_init_if( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator) +{ + if (!hm->compare) { + flecs_name_index_init(hm, allocator); + } } -#endif +bool flecs_name_index_is_init( + const ecs_hashmap_t *hm) +{ + return hm->compare != NULL; +} -/** - * @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. - */ +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); +} -#ifdef FLECS_HTTP +void flecs_name_index_free( + ecs_hashmap_t *map) +{ + if (map) { + flecs_name_index_fini(map); + flecs_bfree(map->hashmap_allocator, map); + } +} -#if defined(ECS_TARGET_WINDOWS) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#pragma comment(lib, "Ws2_32.lib") -#include -#include -#include -typedef SOCKET ecs_http_socket_t; -#else -#include -#include -#include -#include -#include -#include -#include -#include -typedef int ecs_http_socket_t; -#endif +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; +} -#if !defined(MSG_NOSIGNAL) -#define MSG_NOSIGNAL (0) -#endif +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); + } -/* Max length of request method */ -#define ECS_HTTP_METHOD_LEN_MAX (8) + if (!hash) { + hash = flecs_hash(name, length); + } else { + ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); + } -/* Timeout (s) before connection purge */ -#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) + return (ecs_hashed_string_t) { + .value = ECS_CONST_CAST(char*, name), + .length = length, + .hash = hash + }; +} -/* Number of dequeues before purging */ -#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) +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; + } -/* Number of retries receiving request */ -#define ECS_HTTP_REQUEST_RECV_RETRY (10) + ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); + int32_t i, count = ecs_vec_count(&b->keys); -/* Minimum interval between dequeueing requests (ms) */ -#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) + for (i = 0; i < count; i ++) { + ecs_hashed_string_t *key = &keys[i]; + ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); -/* Minimum interval between printing statistics (ms) */ -#define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) + if (hs.length != key->length) { + continue; + } -/* Max length of headers in reply */ -#define ECS_HTTP_REPLY_HEADER_SIZE (1024) + 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; + } + } -/* Receive buffer size */ -#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) + return NULL; +} -/* Max length of request (path + query + headers + body) */ -#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) +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; +} -/* Total number of outstanding send requests */ -#define ECS_HTTP_SEND_QUEUE_MAX (256) +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; + } -/* Cache invalidation timeout (s) */ -#define ECS_HTTP_CACHE_TIMEOUT ((ecs_ftime_t)1.0) + 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; + } + } +} -/* Cache entry purge timeout (s) */ -#define ECS_HTTP_CACHE_PURGE_TIMEOUT ((ecs_ftime_t)10.0) +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; + } -/* 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; + 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; + } + } -/* 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; + /* Record must already have been in the index */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); +} -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; +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); -typedef struct ecs_http_request_key_t { - const char *array; - ecs_size_t count; -} ecs_http_request_key_t; + 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); + } + } -typedef struct ecs_http_request_entry_t { - char *content; - int32_t content_length; - ecs_ftime_t time; -} ecs_http_request_entry_t; + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + map, &key, uint64_t); + *((uint64_t*)hmr.value) = id; +error: + return; +} -/* HTTP server struct */ -struct ecs_http_server_t { - bool should_run; - bool running; +/** + * @file datastructures/sparse.c + * @brief Sparse set data structure. + */ - ecs_http_socket_t sock; - ecs_os_mutex_t lock; - ecs_os_thread_t thread; - ecs_http_reply_action_t callback; - void *ctx; +/** 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)) - ecs_sparse_t connections; /* sparse */ - ecs_sparse_t requests; /* sparse */ +/** This computes the offset of an index inside a page */ +#define OFFSET(index) ((int32_t)index & (FLECS_SPARSE_PAGE_SIZE - 1)) - bool initialized; +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) - uint16_t port; - const char *ipaddr; +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; - double dequeue_timeout; /* used to not lock request queue too often */ - double stats_timeout; /* used for periodic reporting of statistics */ +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; - 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; + 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_hashmap_t request_cache; -}; + ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); -/** Fragment state, used by HTTP request parser */ -typedef enum { - HttpFragStateBegin, - HttpFragStateMethod, - HttpFragStatePath, - HttpFragStateVersion, - HttpFragStateHeaderStart, - HttpFragStateHeaderName, - HttpFragStateHeaderValueStart, - HttpFragStateHeaderValue, - HttpFragStateCR, - HttpFragStateCRLF, - HttpFragStateCRLFCR, - HttpFragStateBody, - HttpFragStateDone -} HttpFragState; + 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); -/** 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; + /* 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); -/** Extend public connection type with fragment data */ -typedef struct { - ecs_http_connection_t pub; - ecs_http_socket_t sock; + /* 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); - /* 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; + ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); -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; + return result; +} static -ecs_size_t http_send( - ecs_http_socket_t sock, - const void *buf, - ecs_size_t size, - int flags) +void flecs_sparse_page_free( + ecs_sparse_t *sparse, + ecs_page_t *page) { - 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 + 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_size_t http_recv( - ecs_http_socket_t sock, - void *buf, - ecs_size_t size, - int flags) +ecs_page_t* flecs_sparse_get_page( + const ecs_sparse_t *sparse, + int32_t page_index) { - 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); + ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); + if (page_index >= ecs_vec_count(&sparse->pages)) { + return NULL; } - - return ret; + return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index); } static -void http_sock_set_timeout( - ecs_http_socket_t sock, - int32_t timeout_ms) +ecs_page_t* flecs_sparse_get_or_create_page( + ecs_sparse_t *sparse, + int32_t page_index) { - 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)); + 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 http_sock_keep_alive( - ecs_http_socket_t sock) +void flecs_sparse_grow_dense( + ecs_sparse_t *sparse) { - 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)); - } + ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } static -void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { - (void)sock; -#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 +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 -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) +void flecs_sparse_assign_index( + ecs_page_t * page, + uint64_t * dense_array, + uint64_t index, + int32_t dense) { - 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); - return getnameinfo(addr, (uint32_t)addr_len, host, (uint32_t)host_len, - port, (uint32_t)port_len, flags); + /* 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 -int http_bind( - ecs_http_socket_t sock, - const struct sockaddr* addr, - ecs_size_t addr_len) +uint64_t flecs_sparse_get_id( + const ecs_sparse_t *sparse) { - ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); - return bind(sock, addr, (uint32_t)addr_len); + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + return sparse->max_id; } static -bool http_socket_is_valid( - ecs_http_socket_t sock) +void flecs_sparse_set_id( + ecs_sparse_t *sparse, + uint64_t value) { -#if defined(ECS_TARGET_WINDOWS) - return sock != INVALID_SOCKET; -#else - return sock >= 0; -#endif + /* 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; } -#if defined(ECS_TARGET_WINDOWS) -#define HTTP_SOCKET_INVALID INVALID_SOCKET -#else -#define HTTP_SOCKET_INVALID (-1) -#endif - +/* Pair dense id with new sparse id */ static -void http_close( - ecs_http_socket_t *sock) +uint64_t flecs_sparse_create_id( + ecs_sparse_t *sparse, + int32_t dense) { - ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t index = flecs_sparse_inc_id(sparse); + flecs_sparse_grow_dense(sparse); -#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; + 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 -ecs_http_socket_t http_accept( - ecs_http_socket_t sock, - struct sockaddr* addr, - ecs_size_t *addr_len) +uint64_t flecs_sparse_new_index( + ecs_sparse_t *sparse) { - 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; + 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 http_reply_fini(ecs_http_reply_t* reply) { - ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(reply->body.content); +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 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); +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); } -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; +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; - if (http_socket_is_valid(conn->sock)) { - http_close(&conn->sock); - } + 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; - flecs_sparse_remove_t(&conn->pub.server->connections, - ecs_http_connection_impl_t, conn_id); -} + /* 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; -// 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); + result->count = 1; } -static -void http_decode_url_str( - char *str) +void flecs_sparse_clear( + ecs_sparse_t *sparse) { - 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 ++; + 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); } } - dst[0] = '\0'; + + ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); + + sparse->count = 1; + sparse->max_id = 0; } -static -void http_parse_method( - ecs_http_fragment_t *frag) +void flecs_sparse_fini( + ecs_sparse_t *sparse) { - 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_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_strbuf_reset(&frag->buf); + + ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); + ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } -static -bool http_header_writable( - ecs_http_fragment_t *frag) +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) { - return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_sparse_new_index(sparse); } -static -void http_header_buf_reset( - ecs_http_fragment_t *frag) +void* flecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t size) { - frag->header_buf[0] = '\0'; - frag->header_buf_ptr = frag->header_buf; + 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)); } -static -void http_header_buf_append( - ecs_http_fragment_t *frag, - char ch) +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse) { - 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'; - } + 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]; } -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)); -} +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; -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; + 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]; - int32_t count_1 = type_1->count; - int32_t count_2 = type_2->count; + 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; - if (count_1 != count_2) { - return (count_1 > count_2) - (count_1 < count_2); + /* 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 ecs_os_memcmp(type_1->array, type_2->array, count_1); + return DATA(page->data, sparse->size, offset); } -static -ecs_http_request_entry_t* http_find_request_entry( - ecs_http_server_t *srv, - const char *array, - int32_t count) +void* flecs_sparse_ensure_fast( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index_long) { - ecs_http_request_key_t key; - key.array = array; - key.count = count; + 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; - ecs_time_t t = {0, 0}; - ecs_http_request_entry_t *entry = flecs_hashmap_get( - &srv->request_cache, &key, ecs_http_request_entry_t); + 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 (entry) { - ecs_ftime_t tf = (ecs_ftime_t)ecs_time_measure(&t); - if ((tf - entry->time) < ECS_HTTP_CACHE_TIMEOUT) { - return entry; + 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 NULL; + + return DATA(page->data, sparse->size, offset); } -static -void http_insert_request_entry( - ecs_http_server_t *srv, - ecs_http_request_impl_t *req, - ecs_http_reply_t *reply) +void flecs_sparse_remove( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - int32_t content_length = ecs_strbuf_written(&reply->body); - if (!content_length) { + 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; } - 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; + 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 { - ecs_os_free(entry->content); + /* Element is not paired and thus not alive, nothing to be done */ + return; } +} - 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); - ecs_strbuf_appendstrn(&reply->body, - entry->content, entry->content_length); +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 */ + } } -static -char* http_decode_request( - ecs_http_request_impl_t *req, - ecs_http_fragment_t *frag) +void* flecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t size, + int32_t dense_index) { - ecs_os_zeromem(req); + 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; - char *res = ecs_strbuf_get(&frag->buf); - if (!res) { - return NULL; + 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; } - req->pub.method = frag->method; - req->pub.path = res + 1; - http_decode_url_str(req->pub.path); + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + if (!dense || (dense >= sparse->count)) { + return false; + } - if (frag->body_offset) { - req->pub.body = &res[frag->body_offset]; + 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; } - 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]]; + + 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; } - 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]]; - http_decode_url_str((char*)req->pub.params[i].value); + + int32_t offset = OFFSET(index); + int32_t dense = page->sparse[offset]; + if (!dense || (dense >= sparse->count)) { + return NULL; } - req->pub.header_count = frag->header_count; - req->pub.param_count = frag->param_count; - req->res = res; - req->req_len = frag->header_offsets[0]; + 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; + } - return res; + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, sparse->size, offset); } -static -ecs_http_request_entry_t* http_enqueue_request( - ecs_http_connection_impl_t *conn, - uint64_t conn_id, - ecs_http_fragment_t *frag) +void* flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - ecs_http_server_t *srv = conn->pub.server; + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; - ecs_os_mutex_lock(srv->lock); - bool is_alive = conn->pub.id == conn_id; + 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); - 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; + 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; - /* 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_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); +} - 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); - } +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; } - ecs_os_mutex_unlock(srv->lock); - 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); } -static -bool http_parse_request( - ecs_http_fragment_t *frag, - const char* req_frag, - ecs_size_t req_frag_len) +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) { - 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->buf.max = ECS_HTTP_METHOD_LEN_MAX; - frag->state = HttpFragStateMethod; - frag->header_buf_ptr = frag->header_buf; - /* fallthrough */ - case HttpFragStateMethod: - if (c == ' ') { - http_parse_method(frag); - frag->state = HttpFragStatePath; - frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; - } 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; - /* fallthrough */ - 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; - } - /* fallthrough */ - 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 (!sparse || !sparse->count) { + return 0; } - if (frag->state == HttpFragStateDone) { - return true; - } else { - return false; - } + return sparse->count - 1; } -static -ecs_http_send_request_t* http_send_queue_post( - ecs_http_server_t *srv) +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) { - /* 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) { + 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; } +} - /* Don't enqueue new requests if server is shutting down */ - if (!srv->should_run) { - return NULL; - } +void ecs_sparse_init( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + flecs_sparse_init(sparse, NULL, NULL, elem_size); +} - /* Return element at end of the queue */ - ecs_http_send_request_t *result = &sq->requests[sq->head]; - sq->head = next; - return result; +void* ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + return flecs_sparse_add(sparse, elem_size); } -static -ecs_http_send_request_t* http_send_queue_get( - ecs_http_server_t *srv) +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse) { - ecs_os_mutex_lock(srv->lock); - ecs_http_send_queue_t *sq = &srv->send_queue; - if (sq->tail == sq->head) { - return NULL; - } + return flecs_sparse_last_id(sparse); +} - 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; +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_count(sparse); } -static -void* http_server_send_queue(void* arg) { - ecs_http_server_t *srv = arg; - int32_t wait_ms = srv->send_queue.wait_ms; +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); +} - /* 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); +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); +} - if (http_socket_is_valid(sock)) { - bool error = false; +/** + * @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. + */ - 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); - } +#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) - http_close(&sock); - } else { - ecs_err("http: invalid socket\n"); - } +int64_t ecs_stack_allocator_alloc_count = 0; +int64_t ecs_stack_allocator_free_count = 0; - ecs_os_free(content); - ecs_os_free(headers); - } - } - return NULL; +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; } -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) +void* flecs_stack_alloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) { - 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"); + 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); } - if (content_len >= 0) { - ecs_strbuf_appendlit(hdrs, "Content-Length: "); - ecs_strbuf_append(hdrs, "%d", content_len); - ecs_strbuf_appendlit(hdrs, "\r\n"); - } + 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; - 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"); + 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; } - ecs_strbuf_mergebuff(hdrs, extra_headers); + page->sp = next_sp; + result = ECS_OFFSET(page->data, sp); - ecs_strbuf_appendlit(hdrs, "\r\n"); +done: +#ifdef FLECS_SANITIZE + ecs_os_memset(result, 0xAA, size); +#endif + return result; } -static -void http_send_reply( - ecs_http_connection_impl_t* conn, - ecs_http_reply_t* reply, - bool preflight) +void* flecs_stack_calloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) { - ecs_strbuf_t hdrs = ECS_STRBUF_INIT; - char *content = ecs_strbuf_get(&reply->body); - int32_t content_length = reply->body.length - 1; + void *ptr = flecs_stack_alloc(stack, size, align); + ecs_os_memset(ptr, 0, size); + return ptr; +} - /* Use asynchronous send queue for outgoing data so send operations won't - * hold up main thread */ - ecs_http_send_request_t *req = NULL; +void flecs_stack_free( + void *ptr, + ecs_size_t size) +{ + if (size > ECS_STACK_PAGE_SIZE) { + ecs_os_free(ptr); + } +} - 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); - } +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; } - http_append_send_headers(&hdrs, reply->code, reply->status, - reply->content_type, &reply->headers, content_length, preflight); - char *headers = ecs_strbuf_get(&hdrs); - ecs_size_t headers_length = ecs_strbuf_written(&hdrs); + 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); - 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); + 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; } - /* 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; + /* 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); - /* Take ownership of values */ - reply->body.content = NULL; - conn->sock = HTTP_SOCKET_INVALID; + 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); } -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) +void flecs_stack_reset( + ecs_stack_t *stack) { - ecs_size_t bytes_read; - char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; - ecs_http_fragment_t frag = {0}; - int32_t retries = 0; + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL); + stack->tail_page = &stack->first; + stack->first.sp = 0; + stack->tail_cursor = NULL; +} - 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; - } +void flecs_stack_init( + ecs_stack_t *stack) +{ + ecs_os_zeromem(stack); + stack->tail_page = &stack->first; + stack->first.data = NULL; +} - 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 = 200; - reply.content_type = NULL; - 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); +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); - /* Lock was transferred from enqueue_request */ - ecs_os_mutex_unlock(srv->lock); - } - } - } else { - ecs_os_linc(&ecs_http_request_invalid_count); + 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)); +} - ecs_os_sleep(0, 10 * 1000 * 1000); - } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); +/** + * @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. + */ - if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { - http_close(&sock); - } +#include -done: - ecs_strbuf_reset(&frag.buf); -} +/** + * stm32tpl -- STM32 C++ Template Peripheral Library + * Visit https://github.com/antongus/stm32tpl for new versions + * + * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA + */ -typedef struct { - ecs_http_connection_impl_t *conn; - uint64_t id; -} http_conn_res_t; +#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 -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) +char* flecs_strbuf_itoa( + char *buf, + int64_t v) { - http_sock_set_timeout(sock_conn, 100); - http_sock_keep_alive(sock_conn); - http_sock_nonblock(sock_conn, true); + char *ptr = buf; + char * p1; + char c; - /* 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); + if (!v) { + *ptr++ = '0'; + } else { + if (v < 0) { + ptr[0] = '-'; + ptr ++; + v *= -1; + } - char *remote_host = conn->pub.host; - char *remote_port = conn->pub.port; + char *p = ptr; + while (v) { + int64_t vdiv = v / 10; + int64_t vmod = v - (vdiv * 10); + p[0] = (char)('0' + vmod); + p ++; + v = vdiv; + } - /* 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"); - } + p1 = p; - 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 }; + while (p > ptr) { + c = *--p; + *p = *ptr; + *ptr++ = c; + } + ptr = p1; + } + return ptr; } static -void http_accept_connections( - ecs_http_server_t* srv, - const struct sockaddr* addr, - ecs_size_t addr_len) +int flecs_strbuf_ftoa( + ecs_strbuf_t *out, + double f, + int precision, + char nan_delim) { -#ifdef ECS_TARGET_WINDOWS - /* If on Windows, test if winsock needs to be initialized */ - SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (SOCKET_ERROR == 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; - } + char buf[64]; + char * ptr = buf; + char c; + int64_t intPart; + int64_t exp = 0; - 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 (ecs_os_isnan(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "NaN"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendlit(out, "NaN"); } - - 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)); - } + } + if (ecs_os_isinf(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "Inf"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendlit(out, "Inf"); } + } - 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; - } + if (precision > MAX_PRECISION) { + precision = MAX_PRECISION; + } - http_sock_set_timeout(sock, 1000); + if (f < 0) { + f = -f; + *ptr++ = '-'; + } - srv->sock = sock; + 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; + } - result = listen(srv->sock, SOMAXCONN); - if (result) { - ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", - SOMAXCONN, ecs_os_strerror(errno)); - } + if (precision) { + f += rounders[precision]; + } - ecs_trace("http: listening for incoming connections on '%s:%s'", - addr_host, addr_port); - } else { - ecs_dbg_2("http: server shut down while initializing"); + /* Make sure that number can be represented as 64bit int, increase exp */ + while (f > INT64_MAX_F) { + f /= 1000 * 1000 * 1000; + exp += 9; } - ecs_os_mutex_unlock(srv->lock); - struct sockaddr_storage remote_addr; - ecs_size_t remote_addr_len = 0; + intPart = (int64_t)f; + f -= (double)intPart; - 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); + ptr = flecs_strbuf_itoa(ptr, intPart); - if (!http_socket_is_valid(sock_conn)) { - if (srv->should_run) { - ecs_dbg("http: connection attempt failed: %s", - ecs_os_strerror(errno)); - } - continue; - } + if (precision) { + *ptr++ = '.'; + while (precision--) { + f *= 10.0; + c = (char)f; + *ptr++ = (char)('0' + c); + f -= c; + } + } + *ptr = 0; - 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); + /* Remove trailing 0s */ + while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { + ptr[-1] = '\0'; + ptr --; } - -done: - ecs_os_mutex_lock(srv->lock); - if (http_socket_is_valid(sock) && errno != EBADF) { - http_close(&sock); - srv->sock = sock; + if (ptr != buf && ptr[-1] == '.') { + ptr[-1] = '\0'; + ptr --; } - 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)); + /* 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 --; } - http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); - return NULL; -} + if (exp || ((ptr - cur) > EXP_THRESHOLD)) { + cur[0] = '\0'; + exp += (ptr - cur); + ptr = cur; + } -static -void http_do_request( - ecs_http_server_t *srv, - ecs_http_reply_t *reply, - const ecs_http_request_impl_t *req) -{ - 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 (exp) { + char *p1 = &buf[1]; + if (nan_delim) { + ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); + buf[0] = nan_delim; + p1 ++; } - } -} -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; + /* Make sure that exp starts after first character */ + c = p1[0]; - 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); + 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 { - 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); + ptr = p1; } - 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); -} + ptr[0] = 'e'; + ptr = flecs_strbuf_itoa(ptr + 1, exp); -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) > ECS_HTTP_CACHE_PURGE_TIMEOUT)) { - ecs_http_request_key_t *key = &keys[i]; - ecs_os_free((char*)key->array); - ecs_os_free(entry->content); - flecs_hm_bucket_remove(&srv->request_cache, bucket, - ecs_map_key(&it), i); - } + if (nan_delim) { + ptr[0] = nan_delim; + ptr ++; } - } - if (fini) { - flecs_hashmap_fini(&srv->request_cache); + ptr[0] = '\0'; } + + return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } +/* Add an extra element to the buffer */ static -int32_t http_dequeue_requests( - ecs_http_server_t *srv, - double delta_time) +void flecs_strbuf_grow( + ecs_strbuf_t *b) { - 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; + /* Allocate new element */ + ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = true; + e->super.buf = e->buf; + e->super.pos = 0; + e->super.next = NULL; } -const char* ecs_http_get_header( - const ecs_http_request_t* req, - const char* name) +/* Add an extra dynamic element */ +static +void flecs_strbuf_grow_str( + ecs_strbuf_t *b, + const char *str, + char *alloc_str, + int32_t size) { - 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; + /* Allocate new element */ + ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = false; + e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); + e->super.next = NULL; + e->super.buf = ECS_CONST_CAST(char*, str); + e->alloc_str = alloc_str; } -const char* ecs_http_get_param( - const ecs_http_request_t* req, - const char* name) +static +char* flecs_strbuf_ptr( + ecs_strbuf_t *b) { - 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; - } + if (b->buf) { + return &b->buf[b->current->pos]; + } else { + return &b->current->buf[b->current->pos]; } - return NULL; } -ecs_http_server_t* ecs_http_server_init( - const ecs_http_server_desc_t *desc) +/* Compute the amount of space left in the current element */ +static +int32_t flecs_strbuf_memLeftInCurrentElement( + ecs_strbuf_t *b) { - 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->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; + if (b->current->buffer_embedded) { + return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; + } else { + return 0; } - - 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) +/* Compute the amount of space left */ +static +int32_t flecs_strbuf_memLeft( + ecs_strbuf_t *b) { - if (srv->should_run) { - ecs_http_server_stop(srv); + if (b->max) { + return b->max - b->size - b->current->pos; + } else { + return INT_MAX; } - 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) +static +void flecs_strbuf_init( + ecs_strbuf_t *b) { - 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; + /* Initialize buffer structure only once */ + if (!b->elementCount) { + b->size = 0; + b->firstElement.super.next = NULL; + b->firstElement.super.pos = 0; + b->firstElement.super.buffer_embedded = true; + b->firstElement.super.buf = b->firstElement.buf; + b->elementCount ++; + b->current = (ecs_strbuf_element*)&b->firstElement; } - - return 0; -error: - return -1; } -void ecs_http_server_stop( - ecs_http_server_t* srv) +/* Append a format string to a buffer */ +static +bool flecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* str, + va_list args) { - 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"); + bool result = true; + va_list arg_cpy; - ecs_os_mutex_lock(srv->lock); - srv->should_run = false; - if (http_socket_is_valid(srv->sock)) { - http_close(&srv->sock); + if (!str) { + return result; } - 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"); + flecs_strbuf_init(b); - /* 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)); - } + int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = flecs_strbuf_memLeft(b); - /* 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)); + if (!memLeft) { + return false; } - 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; + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; - 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 ++; - } + va_copy(arg_cpy, args); + memRequired = vsnprintf( + flecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); - 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; - } + ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); -error: - return; -} + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* If string is a format string, a new buffer of size memRequired is + * needed to re-evaluate the format string and only use the part that + * wasn't already copied to the previous element */ + if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { + /* Resulting string fits in standard-size buffer. Note that the + * entire string needs to fit, not just the remainder, as the + * format string cannot be partially evaluated */ + flecs_strbuf_grow(b); -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); - } + /* Copy entire string to new buffer */ + ecs_os_vsprintf(flecs_strbuf_ptr(b), str, arg_cpy); - ecs_http_fragment_t frag = {0}; - if (!http_parse_request(&frag, req, len)) { - ecs_strbuf_reset(&frag.buf); - reply_out->code = 400; - return -1; - } + /* Ignore the part of the string that was copied into the + * previous buffer. The string copied into the new buffer could + * be memmoved so that only the remainder is left, but that is + * most likely more expensive than just keeping the entire + * string. */ - ecs_http_request_impl_t request; - char *res = http_decode_request(&request, &frag); - if (!res) { - reply_out->code = 400; - return -1; + /* Update position in buffer */ + b->current->pos += memRequired; + } else { + /* Resulting string does not fit in standard-size buffer. + * Allocate a new buffer that can hold the entire string. */ + char *dst = ecs_os_malloc(memRequired + 1); + ecs_os_vsprintf(dst, str, arg_cpy); + flecs_strbuf_grow_str(b, dst, dst, memRequired); + } } - http_do_request(srv, reply_out, &request); - ecs_os_free(res); - - return (reply_out->code >= 400) ? -1 : 0; -} + va_end(arg_cpy); -int ecs_http_server_request( - ecs_http_server_t* srv, - const char *method, - const char *req, - ecs_http_reply_t *reply_out) -{ - ecs_strbuf_t reqbuf = ECS_STRBUF_INIT; - ecs_strbuf_appendstr_zerocpy_const(&reqbuf, method); - ecs_strbuf_appendlit(&reqbuf, " "); - ecs_strbuf_appendstr_zerocpy_const(&reqbuf, req); - ecs_strbuf_appendlit(&reqbuf, " HTTP/1.1\r\n\r\n"); - int32_t len = ecs_strbuf_written(&reqbuf); - char *reqstr = ecs_strbuf_get(&reqbuf); - int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); - ecs_os_free(reqstr); - return result; + return flecs_strbuf_memLeft(b) > 0; } -void* ecs_http_server_ctx( - ecs_http_server_t* srv) +static +bool flecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str, + int n) { - return srv->ctx; -} - -#endif - - /** - * @file addons/rules/compile.c - * @brief Compile rule program from filter. - */ - - /** - * @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" + flecs_strbuf_init(b); -/* -- 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; + int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = flecs_strbuf_memLeft(b); + if (memLeft <= 0) { + return false; + } -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 */ - const char *name; /* variable name */ -#ifdef FLECS_DEBUG - const char *label; /* for debugging */ -#endif -} ecs_rule_var_t; + /* Never write more than what the buffer can store */ + if (n > memLeft) { + n = memLeft; + } -/* -- Instruction kinds -- */ -typedef enum { - EcsRuleAnd, /* And operator: find or match id against variable source */ - EcsRuleAndId, /* And operator for fixed id (no wildcards/variables) */ - EcsRuleWith, /* Match id against fixed or variable source */ - EcsRuleAndAny, /* And operator with support for matching Any src/id */ - EcsRuleTrav, /* Support for transitive/reflexive queries */ - 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 */ - EcsRuleUnion, /* Combine output of multiple operations */ - EcsRuleEnd, /* Used to denote end of EcsRuleUnion block */ - EcsRuleNot, /* Sets iterator state after term was not matched */ - 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 */ - EcsRuleSetVars, /* Populate it.sources from variables */ - EcsRuleSetThis, /* Populate This entity variable */ - EcsRuleSetFixed, /* Set fixed source entity ids */ - EcsRuleSetIds, /* Set fixed (component) ids */ - EcsRuleContain, /* Test if table contains entity */ - EcsRulePairEq, /* Test if both elements of pair are the same */ - EcsRuleSetCond, /* Set conditional value for EcsRuleJmpCondFalse */ - EcsRuleJmpCondFalse, /* Jump if condition is false */ - EcsRuleJmpNotSet, /* Jump if variable(s) is not set */ - EcsRuleYield, /* Yield result back to application */ - EcsRuleNothing /* Must be last */ -} ecs_rule_op_kind_t; + if (n <= memLeftInElement) { + /* Element was large enough to fit string */ + ecs_os_strncpy(flecs_strbuf_ptr(b), str, n); + b->current->pos += n; + } else if ((n - memLeftInElement) < memLeft) { + ecs_os_strncpy(flecs_strbuf_ptr(b), str, memLeftInElement); -/* 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) + /* Element was not large enough, but buffer still has space */ + b->current->pos += memLeftInElement; + n -= memLeftInElement; -/* Op flags used to shift EcsRuleIsEntity and EcsRuleIsVar */ -#define EcsRuleSrc 0 -#define EcsRuleFirst 2 -#define EcsRuleSecond 4 + /* Current element was too small, copy remainder into new element */ + if (n < ECS_STRBUF_ELEMENT_SIZE) { + /* A standard-size buffer is large enough for the new string */ + flecs_strbuf_grow(b); -/* References to variable or entity */ -typedef union { - ecs_var_id_t var; - ecs_entity_t entity; -} ecs_rule_ref_t; + /* Copy the remainder to the new buffer */ + if (n) { + /* If a max number of characters to write is set, only a + * subset of the string should be copied to the buffer */ + ecs_os_strncpy( + flecs_strbuf_ptr(b), + str + memLeftInElement, + (size_t)n); + } else { + ecs_os_strcpy(flecs_strbuf_ptr(b), str + memLeftInElement); + } -/* 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; + /* Update to number of characters copied to new buffer */ + b->current->pos += n; + } else { + /* String doesn't fit in a single element, strdup */ + char *remainder = ecs_os_strdup(str + memLeftInElement); + flecs_strbuf_grow_str(b, remainder, remainder, n); + } + } else { + /* Buffer max has been reached */ + return false; + } - /* 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; + return flecs_strbuf_memLeft(b) > 0; +} -/* Cache for storing results of downward traversal */ -typedef struct { - ecs_entity_t entity; - ecs_id_record_t *idr; - int32_t column; -} ecs_trav_elem_t; +static +bool flecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + flecs_strbuf_init(b); -typedef struct { - ecs_id_t id; - ecs_id_record_t *idr; - ecs_vec_t entities; - bool up; -} ecs_trav_cache_t; + int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = flecs_strbuf_memLeft(b); + if (memLeft <= 0) { + return false; + } -/* 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; + if (memLeftInElement) { + /* Element was large enough to fit string */ + flecs_strbuf_ptr(b)[0] = ch; + b->current->pos ++; + } else { + flecs_strbuf_grow(b); + flecs_strbuf_ptr(b)[0] = ch; + b->current->pos ++; + } - /* Eq context */ -typedef struct { - ecs_table_range_t range; - int32_t index; - int16_t name_col; - bool redo; -} ecs_rule_eq_ctx_t; + return flecs_strbuf_memLeft(b) > 0; +} - /* Each context */ -typedef struct { - int32_t row; -} ecs_rule_each_ctx_t; +bool 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); + return flecs_strbuf_vappend(b, fmt, args); +} - /* Setthis context */ -typedef struct { - ecs_table_range_t range; -} ecs_rule_setthis_ctx_t; +bool 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); -/* Ids context */ -typedef struct { - ecs_id_record_t *cur; -} ecs_rule_ids_ctx_t; + va_list args; + va_start(args, fmt); + bool result = flecs_strbuf_vappend(b, fmt, args); + va_end(args); -/* Ctrlflow context (used with Union) */ -typedef struct { - ecs_rule_lbl_t lbl; -} ecs_rule_ctrlflow_ctx_t; + return result; +} -/* Condition context */ -typedef struct { - bool cond; -} ecs_rule_cond_ctx_t; +bool 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); + return flecs_strbuf_appendstr(b, str, len); +} -typedef struct ecs_rule_op_ctx_t { - union { - ecs_rule_and_ctx_t and; - 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_ctrlflow_ctx_t ctrlflow; - ecs_rule_cond_ctx_t cond; - } is; -} ecs_rule_op_ctx_t; +bool ecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_strbuf_appendch(b, ch); +} -typedef struct { - /* Labels used for control flow */ - ecs_rule_lbl_t lbl_union; - ecs_rule_lbl_t lbl_not; - ecs_rule_lbl_t lbl_option; - ecs_rule_lbl_t lbl_cond_eval; - ecs_rule_lbl_t lbl_or; - ecs_rule_lbl_t lbl_none; - ecs_rule_lbl_t lbl_prev; /* If set, use this as default value for prev */ -} ecs_rule_compile_ctrlflow_t; +bool 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); + return ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); +} -/* 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) */ +bool ecs_strbuf_appendflt( + ecs_strbuf_t *b, + double flt, + char nan_delim) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_strbuf_ftoa(b, flt, 10, nan_delim); +} - /* Maintain control flow per scope */ - ecs_rule_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; - ecs_rule_compile_ctrlflow_t *cur; /* Current scope */ +bool ecs_strbuf_appendbool( + ecs_strbuf_t *buffer, + bool v) +{ + ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); + if (v) { + return ecs_strbuf_appendlit(buffer, "true"); + } else { + return ecs_strbuf_appendlit(buffer, "false"); + } +} - 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; +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *b, + char* str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_init(b); + flecs_strbuf_grow_str(b, str, str, 0); + return true; +} -/* 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_rule_lbl_t prev_index; /* Previously evaluated operation */ - ecs_rule_lbl_t jump; /* Set by control flow operations to jump to 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_rule_run_ctx_t; +bool ecs_strbuf_appendstr_zerocpyn( + ecs_strbuf_t *b, + char *str, + int32_t n) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_init(b); + flecs_strbuf_grow_str(b, str, str, n); + return true; +} -typedef struct { - ecs_rule_var_t var; - const char *name; -} ecs_rule_var_cache_t; +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *b, + const char* str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + /* Removes const modifier, but logic prevents changing / delete string */ + flecs_strbuf_init(b); + flecs_strbuf_grow_str(b, str, NULL, 0); + return true; +} -struct ecs_rule_t { - ecs_header_t hdr; /* Poly header */ - ecs_filter_t filter; /* Filter */ +bool ecs_strbuf_appendstr_zerocpyn_const( + 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); + /* Removes const modifier, but logic prevents changing / delete string */ + flecs_strbuf_init(b); + flecs_strbuf_grow_str(b, str, NULL, n); + return true; +} - /* 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 */ +bool 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); + return flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); +} - ecs_rule_op_t *ops; /* Operations */ - int32_t op_count; /* Number of operations */ +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer) +{ + if (src_buffer->elementCount) { + if (src_buffer->buf) { + return ecs_strbuf_appendstrn( + dst_buffer, src_buffer->buf, src_buffer->length); + } else { + ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; - /* Mixins */ - ecs_iterable_t iterable; - ecs_poly_dtor_t dtor; + /* Copy first element as it is inlined in the src buffer */ + ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); -#ifdef FLECS_DEBUG - int32_t var_size; /* Used for out of bounds check during compilation */ -#endif -}; + while ((e = e->next)) { + dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); + *dst_buffer->current->next = *e; + } + } -/* Convert integer to label */ -ecs_rule_lbl_t flecs_itolbl( - int64_t val); + *src_buffer = ECS_STRBUF_INIT; + } -/* 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); + return true; +} -/* Check if variable is written */ -bool flecs_rule_is_written( - ecs_var_id_t var_id, - uint64_t written); +char* ecs_strbuf_get( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); -/* 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); + char* result = NULL; + if (b->elementCount) { + if (b->buf) { + b->buf[b->current->pos] = '\0'; + result = ecs_os_strdup(b->buf); + } else { + void *next = NULL; + int32_t len = b->size + b->current->pos + 1; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; -/* Compile filter to list of operations */ -int flecs_rule_compile( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_rule_t *rule); + result = ecs_os_malloc(len); + char* ptr = result; -/* Get allocator from iterator */ -ecs_allocator_t* flecs_rule_get_allocator( - const ecs_iter_t *it); + do { + ecs_os_memcpy(ptr, e->buf, e->pos); + ptr += e->pos; + next = e->next; + if (e != &b->firstElement.super) { + if (!e->buffer_embedded) { + ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); + } + ecs_os_free(e); + } + } while ((e = next)); -/* Find all entities when traversing downwards */ -void flecs_rule_get_down_cache( - const ecs_rule_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t entity); + result[len - 1] = '\0'; + b->length = len; + } + } else { + result = NULL; + } -/* Find all entities when traversing upwards */ -void flecs_rule_get_up_cache( - const ecs_rule_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_table_t *table); + b->elementCount = 0; -/* Free traversal cache */ -void flecs_rule_trav_cache_fini( - ecs_allocator_t *a, - ecs_trav_cache_t *cache); + b->content = result; -#endif + return result; +} +char *ecs_strbuf_get_small( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); -#ifdef FLECS_RULES + int32_t written = ecs_strbuf_written(b); + ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL); + char *buf = b->firstElement.buf; + buf[written] = '\0'; + return buf; +} -#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ +void ecs_strbuf_reset( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); -static bool flecs_rule_op_is_test[] = { - [EcsRuleAnd] = true, - [EcsRuleAndAny] = true, - [EcsRuleAndId] = true, - [EcsRuleWith] = true, - [EcsRuleTrav] = true, - [EcsRuleContain] = true, - [EcsRulePairEq] = true, - [EcsRuleNothing] = false -}; + if (b->elementCount && !b->buf) { + void *next = NULL; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + do { + next = e->next; + if (e != (ecs_strbuf_element*)&b->firstElement) { + ecs_os_free(e); + } + } while ((e = next)); + } -ecs_rule_lbl_t flecs_itolbl(int64_t val) { - return flecs_ito(int16_t, val); + *b = ECS_STRBUF_INIT; } -static -ecs_var_id_t flecs_itovar(int64_t val) { - return flecs_ito(uint8_t, val); -} +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); -static -ecs_var_id_t flecs_utovar(uint64_t val) { - return flecs_uto(uint8_t, val); -} + b->list_stack[b->list_sp].count = 0; + b->list_stack[b->list_sp].separator = separator; -#ifdef FLECS_DEBUG -#define flecs_set_var_label(var, lbl) (var)->label = lbl -#else -#define flecs_set_var_label(var, lbl) -#endif + 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); + } + } +} -static -bool flecs_rule_is_builtin_pred( - ecs_term_t *term) +void ecs_strbuf_list_pop( + ecs_strbuf_t *b, + const char *list_close) { - if (term->first.flags & EcsIsEntity) { - ecs_entity_t id = term->first.id; - if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { - return true; + 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); } } - return false; } -bool flecs_rule_is_written( - ecs_var_id_t var_id, - uint64_t written) +void ecs_strbuf_list_next( + ecs_strbuf_t *b) { - if (var_id == EcsVarNone) { - return true; + 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 ++; +} + +bool ecs_strbuf_list_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_list_next(b); + return flecs_strbuf_appendch(b, ch); +} + +bool 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); + bool result = flecs_strbuf_vappend(b, fmt, args); + va_end(args); + + return result; +} + +bool 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_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); - return (written & (1ull << var_id)) != 0; + ecs_strbuf_list_next(b); + return ecs_strbuf_appendstr(b, str); } -static -void flecs_rule_write( - ecs_var_id_t var_id, - uint64_t *written) +bool ecs_strbuf_list_appendstrn( + ecs_strbuf_t *b, + const char *str, + int32_t n) { - ecs_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); - *written |= (1ull << var_id); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + return ecs_strbuf_appendstrn(b, str, n); } -static -void flecs_rule_write_ctx( - ecs_var_id_t var_id, - ecs_rule_compile_ctx_t *ctx, - bool cond_write) +int32_t ecs_strbuf_written( + const ecs_strbuf_t *b) { - 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); - } - if (ctx->scope != 0) { - - } + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + if (b->current) { + return b->size + b->current->pos; + } else { + return 0; } } -ecs_flags16_t flecs_rule_ref_flags( - ecs_flags16_t flags, - ecs_flags16_t kind) +/** + * @file datastructures/switch_list.c + * @brief Interleaved linked list for storing mutually exclusive values. + * + * Datastructure that stores N interleaved linked lists in an array. + * This allows for efficient storage of elements with mutually exclusive values. + * Each linked list has a header element which points to the index in the array + * that stores the first node of the list. Each list node points to the next + * array element. + * + * The datastructure 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) { - return (flags >> kind) & (EcsRuleIsVar | EcsRuleIsEntity); + 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 -bool flecs_ref_is_written( - const ecs_rule_op_t *op, - const ecs_rule_ref_t *ref, - ecs_flags16_t kind, - uint64_t written) +static +ecs_switch_header_t* flecs_switch_get_header( + const ecs_switch_t *sw, + uint64_t value) { - 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); + if (value == 0) { + return NULL; } - return false; + return (ecs_switch_header_t*)ecs_map_get(&sw->hdrs, value); } static -bool flecs_rule_var_is_anonymous( - const ecs_rule_t *rule, - ecs_var_id_t var_id) +ecs_switch_header_t *flecs_switch_ensure_header( + ecs_switch_t *sw, + uint64_t value) { - ecs_rule_var_t *var = &rule->vars[var_id]; - return var->anonymous; + 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 -ecs_var_id_t flecs_rule_find_var_id( - const ecs_rule_t *rule, - const char *name, - ecs_var_kind_t kind) +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(name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); - /* Backwards compatibility */ - if (!ecs_os_strcmp(name, "This")) { - name = "this"; + /* 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; } - 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); + /* 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; } - if (kind == EcsVarEntity) { - if (!flecs_name_index_is_init(&rule->evar_index)) { - return EcsVarNone; - } + /* Decrease count of current header */ + hdr->count --; + ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); +} - uint64_t index = flecs_name_index_find( - &rule->evar_index, name, 0, 0); - if (index == 0) { - return EcsVarNone; - } - return flecs_utovar(index); - } +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_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); + ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); + uint64_t *values = ecs_vec_first(&sw->values); - /* 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; + int i; + for (i = 0; i < elements; i ++) { + nodes[i].prev = -1; + nodes[i].next = -1; + values[i] = 0; } +} - return flecs_rule_find_var_id(rule, name, EcsVarTable); +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); } -int32_t ecs_rule_var_count( - const ecs_rule_t *rule) +void flecs_switch_fini( + ecs_switch_t *sw) { - return rule->var_pub_count; + 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); } -int32_t ecs_rule_find_var( - const ecs_rule_t *rule, - const char *name) +void flecs_switch_add( + ecs_switch_t *sw) { - 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; + 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; } -const char* ecs_rule_var_name( - const ecs_rule_t *rule, - int32_t var_id) +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count) { - if (var_id) { - return rule->vars[var_id].name; - } else { - return EcsThisName; + 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; } } -bool ecs_rule_var_is_entity( - const ecs_rule_t *rule, - int32_t var_id) +int32_t flecs_switch_count( + ecs_switch_t *sw) { - return rule->vars[var_id].kind == EcsVarEntity; + ecs_assert(ecs_vec_count(&sw->values) == ecs_vec_count(&sw->nodes), + ECS_INTERNAL_ERROR, NULL); + return ecs_vec_count(&sw->values); } -static -const char* flecs_term_id_var_name( - ecs_term_id_t *term_id) +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count) { - if (!(term_id->flags & EcsIsVariable)) { - return NULL; - } - - if (term_id->id == EcsThis) { - return EcsThisName; + int32_t old_count = ecs_vec_count(&sw->nodes); + if (old_count >= count) { + return; } - return term_id->name; + flecs_switch_set_count(sw, count); } -static -bool flecs_term_id_is_wildcard( - ecs_term_id_t *term_id) +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count) { - if ((term_id->flags & EcsIsVariable) && - ((term_id->id == EcsWildcard) || (term_id->id == EcsAny))) - { - return true; - } - return false; + int32_t old_count = ecs_vec_count(&sw->nodes); + flecs_switch_set_count(sw, old_count + count); } -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) +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value) { - 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; - } - } + 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); - if (kind == EcsVarTable) { - var_index = &rule->tvar_index; - } else { - var_index = &rule->evar_index; - } + uint64_t *values = ecs_vec_first(&sw->values); + uint64_t cur_value = values[element]; - /* 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); - } + /* If the node is already assigned to the value, nothing to be done */ + if (cur_value == value) { + return; } - ecs_rule_var_t *var; - if (vars) { - var = ecs_vec_append_t(NULL, vars, ecs_rule_var_t); - 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]; - var->id = flecs_itovar(rule->var_count); - rule->var_count ++; - } + ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); + ecs_switch_node_t *node = &nodes[element]; - var->kind = flecs_ito(int8_t, kind); - var->name = name; - var->table_id = var_id; - flecs_set_var_label(var, NULL); + 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); - if (name) { - flecs_name_index_init_if(var_index, NULL); - flecs_name_index_ensure(var_index, var->id, name, 0, 0); - var->anonymous = name ? name[0] == '_' : false; - } + flecs_switch_verify_nodes(cur_hdr, nodes); + flecs_switch_verify_nodes(dst_hdr, nodes); - return var->id; -} + /* 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); -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; + if (cur_hdr) { + flecs_switch_remove_node(cur_hdr, nodes, node, element); } - return flecs_rule_add_var(rule, name, vars, kind); -} - -static -void 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 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; - } + /* Now update the node itself by adding it as the first node of dst */ + node->prev = -1; + values[element] = value; - 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 (dst_hdr) { + node->next = dst_hdr->element; - /* If first is a wildcard, insert anonymous variable */ - if (flecs_term_id_is_wildcard(first)) { - anonymous_count ++; - } + /* 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; } - 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); - - /* 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); - var->kind = EcsVarAny; + dst_hdr->element = element; + dst_hdr->count ++; + } +} - anonymous_table_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); - if (var_id != EcsVarNone) { - /* Track of 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); - } + 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]; - 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 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); - 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 ++; - } - } + flecs_switch_verify_nodes(hdr, nodes); + flecs_switch_remove_node(hdr, nodes, node, elem); + } - 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 ++; - } + 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; } - /* 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; + 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; } } } - int32_t 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; + /* Remove element from arrays */ + ecs_vec_remove_t(&sw->nodes, ecs_switch_node_t, elem); + ecs_vec_remove_t(&sw->values, uint64_t, elem); +} - 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 ++; - } - } +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); - var_count = ecs_vec_count(vars); - } + uint64_t *values = ecs_vec_first(&sw->values); + return values[element]; +} - /* Always include spot for This variable, even if rule doesn't use it */ - var_count ++; +ecs_vec_t* flecs_switch_values( + const ecs_switch_t *sw) +{ + return ECS_CONST_CAST(ecs_vec_t*, &sw->values); +} - 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)); +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; } - 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 + return hdr->count; +} - char **var_names = ECS_ELEM(rule_vars, ECS_SIZEOF(ecs_rule_var_t), - var_count + anonymous_count); - rule->var_names = (char**)var_names; +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); - 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; - var_names[0] = (char*)rule_vars[0].name; - rule_vars ++; - var_names ++; - var_count --; + flecs_switch_set(sw, elem_2, v1); + flecs_switch_set(sw, elem_1, v2); +} - 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] = (char*)rule_vars[i].name; - } +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; } - /* Hide anonymous table variables from application */ - rule->var_pub_count -= anonymous_table_count; + return hdr->element; } -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) +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t element) { - if (kind == EcsVarTable || kind == EcsVarEntity) { - return flecs_rule_find_var_id(rule, name, kind); - } + 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_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; - } + ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); - 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; + 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; +} - /* If table var is written, and entity var doesn't exist or is not written, - * return table var */ - ecs_assert(tvar != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - return tvar; +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 } -static -ecs_rule_lbl_t flecs_rule_op_insert( - ecs_rule_op_t *op, - ecs_rule_compile_ctx_t *ctx) +void ecs_vec_fini( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) { - 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_union == -1) { - /* Variables written by previous instruction can't be written by - * this instruction, except when this is a union. */ - elem->written &= ~elem[-1].written; + 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; } +} - if (ctx->cur->lbl_union != -1) { - elem->prev = ctx->cur->lbl_union; - } else if (ctx->cur->lbl_prev != -1) { - elem->prev = ctx->cur->lbl_prev; - ctx->cur->lbl_prev = -1; +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 { - elem->prev = flecs_itolbl(count - 2); + ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_vec_clear(v); } + return v; +} - elem->next = flecs_itolbl(count); - return flecs_itolbl(count - 1); +void ecs_vec_clear( + ecs_vec_t *vec) +{ + vec->count = 0; } -static -int32_t flecs_rule_not_insert( - ecs_rule_op_t *op, - ecs_rule_compile_ctx_t *ctx) +ecs_vec_t ecs_vec_copy( + ecs_allocator_t *allocator, + const ecs_vec_t *v, + ecs_size_t size) { - ecs_rule_op_t *op_last = ecs_vec_last_t(ctx->ops, ecs_rule_op_t); - if (op_last && op_last->kind == EcsRuleNot) { - /* There can be multiple reasons for inserting a Not operation, ensure - * that only one is created. */ - ecs_assert(op_last->field_index == op->field_index, - ECS_INTERNAL_ERROR, NULL); - return ecs_vec_count(ctx->ops) - 1; + 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_rule_op_t not_op = {0}; - not_op.kind = EcsRuleNot; - not_op.field_index = op->field_index; - not_op.first = op->first; - not_op.second = op->second; - not_op.flags = op->flags; - not_op.flags &= (ecs_flags8_t)~(EcsRuleIsVar << EcsRuleSrc); - if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { - not_op.src.entity = op->src.entity; +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); + } } - - return flecs_rule_op_insert(¬_op, ctx); } -static -void flecs_rule_begin_none( - ecs_rule_compile_ctx_t *ctx) +void ecs_vec_set_size( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) { - ctx->cur->lbl_none = flecs_itolbl(ecs_vec_count(ctx->ops)); + 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; + } - ecs_rule_op_t jmp = {0}; - jmp.kind = EcsRuleJmpCondFalse; - flecs_rule_op_insert(&jmp, ctx); + 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; + } + } } -static -void flecs_rule_begin_not( - ecs_rule_compile_ctx_t *ctx) +void ecs_vec_set_min_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) { - ctx->cur->lbl_not = flecs_itolbl(ecs_vec_count(ctx->ops)); + if (elem_count > vec->size) { + ecs_vec_set_size(allocator, vec, size, elem_count); + } } -static -void flecs_rule_end_not( - ecs_rule_op_t *op, - ecs_rule_compile_ctx_t *ctx, - bool update_labels) +void ecs_vec_set_min_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) { - if (ctx->cur->lbl_none != -1) { - ecs_rule_op_t setcond = {0}; - setcond.kind = EcsRuleSetCond; - setcond.other = ctx->cur->lbl_none; - flecs_rule_op_insert(&setcond, ctx); + ecs_vec_set_min_size(allocator, vec, size, elem_count); + if (vec->count < elem_count) { + vec->count = elem_count; } +} - flecs_rule_not_insert(op, ctx); +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)); + } +} - ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); - int32_t i, count = ecs_vec_count(ctx->ops); - if (update_labels) { - for (i = ctx->cur->lbl_not; i < count; i ++) { - ecs_rule_op_t *cur = &ops[i]; - if (flecs_rule_op_is_test[cur->kind]) { - cur->prev = flecs_itolbl(count - 1); - if (i == (count - 2)) { - cur->next = flecs_itolbl(ctx->cur->lbl_not - 1); - } - } +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; } +} - /* After a match was found, return to op before Not operation */ - ecs_rule_op_t *not_ptr = ecs_vec_last_t(ctx->ops, ecs_rule_op_t); - not_ptr->prev = flecs_itolbl(ctx->cur->lbl_not - 1); +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); +} - if (ctx->cur->lbl_none != -1) { - /* setcond */ - ops[count - 2].next = flecs_itolbl(ctx->cur->lbl_none - 1); - /* last actual instruction */ - if (update_labels) { - ops[count - 3].prev = flecs_itolbl(count - 4); - } - /* jfalse */ - ops[ctx->cur->lbl_none].other = - flecs_itolbl(count - 1); /* jump to not */ - /* not */ - ops[count - 1].prev = flecs_itolbl(ctx->cur->lbl_none - 1); +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; } - ctx->cur->lbl_not = -1; - ctx->cur->lbl_none = -1; + ecs_os_memcpy( + ECS_ELEM(v->array, size, index), + ECS_ELEM(v->array, size, v->count), + size); } -static -void flecs_rule_begin_option( - ecs_rule_compile_ctx_t *ctx) +void ecs_vec_remove_last( + ecs_vec_t *v) { - ctx->cur->lbl_option = flecs_itolbl(ecs_vec_count(ctx->ops)); + v->count --; +} - { - ecs_rule_op_t new_op = {0}; - new_op.kind = EcsRuleJmpCondFalse; - flecs_rule_op_insert(&new_op, ctx); - } +int32_t ecs_vec_count( + const ecs_vec_t *v) +{ + return v->count; } -static -void flecs_rule_end_option( - ecs_rule_op_t *op, - ecs_rule_compile_ctx_t *ctx) +int32_t ecs_vec_size( + const ecs_vec_t *v) { - flecs_rule_not_insert(op, ctx); - ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); - int32_t count = ecs_vec_count(ctx->ops); + return v->size; +} - ops[ctx->cur->lbl_option].other = flecs_itolbl(count - 1); - ops[count - 2].next = flecs_itolbl(count); +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); +} - ecs_rule_op_t new_op = {0}; - new_op.kind = EcsRuleSetCond; - new_op.other = ctx->cur->lbl_option; - flecs_rule_op_insert(&new_op, ctx); +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); +} - ctx->cur->lbl_option = -1; +void* ecs_vec_first( + const ecs_vec_t *v) +{ + return v->array; } + static -void flecs_rule_begin_cond_eval( - ecs_rule_op_t *op, - ecs_rule_compile_ctx_t *ctx, - ecs_write_flags_t cond_write_state) +ecs_entity_index_page_t* flecs_entity_index_ensure_page( + ecs_entity_index_t *index, + uint32_t id) { - 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; - cond_mask |= (1ull << first_var); - } - if (flecs_rule_ref_flags(op->flags, EcsRuleSecond) == EcsRuleIsVar) { - second_var = op->second.var; - cond_mask |= (1ull << second_var); - } - if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) == EcsRuleIsVar) { - src_var = op->src.var; - cond_mask |= (1ull << src_var); + 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); } - /* 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 = EcsRuleJmpNotSet; + 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); + } - 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; - } + return page; +} - flecs_rule_op_insert(&jmp_op, ctx); - } else { - ctx->cur->lbl_cond_eval = -1; - } +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)); } -static -void flecs_rule_end_cond_eval( - ecs_rule_op_t *op, - ecs_rule_compile_ctx_t *ctx) +void flecs_entity_index_fini( + ecs_entity_index_t *index) { - if (ctx->cur->lbl_cond_eval == -1) { - return; + 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]); } - - flecs_rule_not_insert(op, ctx); - - ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); - int32_t count = ecs_vec_count(ctx->ops); - ops[ctx->cur->lbl_cond_eval].other = flecs_itolbl(count - 1); - ops[count - 2].next = flecs_itolbl(count); +#endif + ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); + flecs_ballocator_fini(&index->page_allocator); } -static -void flecs_rule_next_or( - ecs_rule_compile_ctx_t *ctx) +ecs_record_t* flecs_entity_index_get_any( + const ecs_entity_index_t *index, + uint64_t entity) { - ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); - int32_t count = ecs_vec_count(ctx->ops); - ops[count - 1].next = FlecsRuleOrMarker; + 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; } -static -void flecs_rule_begin_or( - ecs_rule_compile_ctx_t *ctx) +ecs_record_t* flecs_entity_index_get( + const ecs_entity_index_t *index, + uint64_t entity) { - ctx->cur->lbl_or = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); - flecs_rule_next_or(ctx); + 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; } -static -void flecs_rule_end_or( - ecs_rule_compile_ctx_t *ctx) +ecs_record_t* flecs_entity_index_try_get_any( + const ecs_entity_index_t *index, + uint64_t entity) { - flecs_rule_next_or(ctx); + 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_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); - int32_t i, count = ecs_vec_count(ctx->ops); - int32_t prev_or = -2; - for (i = ctx->cur->lbl_or; i < count; i ++) { - if (ops[i].next == FlecsRuleOrMarker) { - if (prev_or != -2) { - ops[prev_or].prev = flecs_itolbl(i); - } - ops[i].next = flecs_itolbl(count); - prev_or = i; - } + ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index)[0]; + if (!page) { + return NULL; } - ops[count - 1].prev = flecs_itolbl(ctx->cur->lbl_or - 1); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + if (!r->dense) { + return NULL; + } - /* Set prev of next instruction to before the start of the OR chain */ - ctx->cur->lbl_prev = flecs_itolbl(ctx->cur->lbl_or - 1); - ctx->cur->lbl_or = -1; + return r; } -static -void flecs_rule_begin_union( - ecs_rule_compile_ctx_t *ctx) +ecs_record_t* flecs_entity_index_try_get( + const ecs_entity_index_t *index, + uint64_t entity) { - ecs_rule_op_t op = {0}; - op.kind = EcsRuleUnion; - ctx->cur->lbl_union = flecs_rule_op_insert(&op, ctx); + 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; } -static -void flecs_rule_end_union( - ecs_rule_compile_ctx_t *ctx) +ecs_record_t* flecs_entity_index_ensure( + ecs_entity_index_t *index, + uint64_t entity) { - flecs_rule_next_or(ctx); + 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]; - ecs_rule_op_t op = {0}; - op.kind = EcsRuleEnd; - ctx->cur->lbl_union = -1; - ecs_rule_lbl_t next = flecs_rule_op_insert(&op, ctx); - - ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); - int32_t i = ecs_vec_count(ctx->ops) - 2; - for (; i >= 0 && (ops[i].kind != EcsRuleUnion); i --) { - if (ops[i].next == FlecsRuleOrMarker) { - ops[i].next = next; + 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; } - ops[next].prev = flecs_itolbl(i); - ops[i].next = next; -} + ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); -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); + /* 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; } -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) +void flecs_entity_index_remove( + ecs_entity_index_t *index, + uint64_t entity) { - /* 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); + 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; + } - /* 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); + 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); - /* 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); + 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); } -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) +void flecs_entity_index_set_generation( + ecs_entity_index_t *index, + uint64_t entity) { - /* 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; + 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; } - 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); +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; + } } -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 flecs_entity_index_is_alive( + const ecs_entity_index_t *index, + uint64_t entity) { - (void)world; + return flecs_entity_index_try_get(index, entity) != NULL; +} - if (!ecs_term_id_is_set(term_id)) { - return; +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 (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); - } else { - 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 the id exists, it must be alive */ + return r->dense < index->alive_count; +} - if (term_id->flags & EcsIsEntity) { - op->flags |= (ecs_flags8_t)(EcsRuleIsEntity << ref_kind); - ref->entity = term_id->id; - } +bool flecs_entity_index_exists( + const ecs_entity_index_t *index, + uint64_t entity) +{ + return flecs_entity_index_try_get_any(index, entity) != NULL; } -static -bool 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) +uint64_t flecs_entity_index_new_id( + ecs_entity_index_t *index) { - ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); - bool written = false; + if (index->alive_count != ecs_vec_count(&index->dense)) { + /* Recycle id */ + return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; + } - 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)) { - flecs_rule_insert_each(tvar, var_id, ctx, cond_write); + /* Create new id */ + uint32_t id = (uint32_t)++ index->max_id; + ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; - /* Variable was written, just not as entity */ - written = true; - } - } + 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); - written |= flecs_rule_is_written(var_id, ctx->written); + return id; +} - /* After evaluating a term, a used variable is always written */ - flecs_rule_write(var_id, &op->written); - flecs_rule_write_ctx(var_id, ctx, cond_write); - } else { - /* If it's not a variable, it's always written */ - written = true; +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; } - return written; + index->alive_count = new_count; + return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } -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) +void flecs_entity_index_set_size( + ecs_entity_index_t *index, + int32_t size) { - 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); - } + ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); } -static -void flecs_rule_insert_pair_eq( - int32_t field_index, - ecs_rule_compile_ctx_t *ctx) +int32_t flecs_entity_index_count( + const ecs_entity_index_t *index) { - ecs_rule_op_t contains = {0}; - contains.kind = EcsRulePairEq; - contains.field_index = flecs_ito(int8_t, field_index); - flecs_rule_op_insert(&contains, ctx); + return index->alive_count - 1; } -static -bool flecs_rule_term_fixed_id( - ecs_filter_t *filter, - ecs_term_t *term) +int32_t flecs_entity_index_size( + const ecs_entity_index_t *index) { - /* Transitive/inherited terms have variable ids */ - if (term->flags & (EcsTermTransitive|EcsTermIdInherited)) { - return false; - } + return ecs_vec_count(&index->dense) - 1; +} - /* Or terms can match different ids */ - if (term->oper == EcsOr) { - return false; - } - if ((term != filter->terms) && term[-1].oper == EcsOr) { - return false; - } +int32_t flecs_entity_index_not_alive_count( + const ecs_entity_index_t *index) +{ + return ecs_vec_count(&index->dense) - index->alive_count; +} - /* Wildcards can assume different ids */ - if (ecs_id_is_wildcard(term->id)) { - return false; +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); + } } - /* Any terms can have fixed ids, but they require special handling */ - if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { - return false; - } + ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); - /* First terms that are Not or Optional require special handling */ - if (term->oper == EcsNot || term->oper == EcsOptional) { - if (term == filter->terms) { - return false; - } - } + index->alive_count = 1; + index->max_id = 0; +} - return true; +const uint64_t* flecs_entity_index_ids( + const ecs_entity_index_t *index) +{ + return ecs_vec_get_t(&index->dense, uint64_t, 1); } static -int flecs_rule_compile_builtin_pred( - ecs_term_t *term, - ecs_rule_op_t *op, - ecs_write_flags_t write_state) +void flecs_entity_index_copy_intern( + ecs_entity_index_t * dst, + const ecs_entity_index_t * src) { - 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}; + flecs_entity_index_set_size(dst, flecs_entity_index_size(src)); + const uint64_t *ids = flecs_entity_index_ids(src); - ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); + 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); + } - 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]); + 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; } - 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; + flecs_entity_index_init(src->allocator, dst); + flecs_entity_index_copy_intern(dst, src); +} - if (flags_2nd & EcsRuleIsVar) { - if (!(write_state & (1ull << op->second.var))) { - ecs_err("uninitialized variable '%s' on right-hand side of " - "equality operator", term->second.name); - return -1; - } +void flecs_entity_index_restore( + ecs_entity_index_t *dst, + const ecs_entity_index_t *src) +{ + if (!src) { + return; } - return 0; + 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 -void flecs_rule_compile_push( - ecs_rule_compile_ctx_t *ctx) +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) { - ctx->cur = &ctx->ctrlflow[++ ctx->scope]; - ctx->cur->lbl_union = -1; - ctx->cur->lbl_prev = -1; - ctx->cur->lbl_not = -1; - ctx->cur->lbl_none = -1; + return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); } static -void flecs_rule_compile_pop( - ecs_rule_compile_ctx_t *ctx) +void flecs_id_record_elem_insert( + ecs_id_record_t *head, + ecs_id_record_t *idr, + ecs_id_record_elem_t *elem) { - ctx->cur = &ctx->ctrlflow[-- ctx->scope]; + 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 -int flecs_rule_compile_term( +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_rule_t *rule, - ecs_term_t *term, - ecs_rule_compile_ctx_t *ctx) + ecs_id_record_t *idr, + ecs_id_t wildcard, + ecs_id_record_t *widr) { - bool first_term = term == rule->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 cond_write = term->oper == EcsOptional; - bool builtin_pred = flecs_rule_is_builtin_pred(term); - bool is_not = (term->oper == EcsNot) && !builtin_pred; - ecs_rule_op_t op = {0}; + 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 (!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 (term->oper == EcsNot) { - ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); - } else { - ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); - } - } else if (term->first.id == EcsScopeClose) { - flecs_rule_compile_pop(ctx); - if (ctx->scope_is_not & (ecs_flags32_t)(1ull << ctx->scope)) { - op.field_index = -1; - flecs_rule_end_not(&op, ctx, false); - } - } else { - /* Nothing to be done */ + 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); } - 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 - rule->filter.terms); +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 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; + 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 { - if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { - op.kind = EcsRuleAndAny; + 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); } } +} - /* If term has fixed id, insert simpler instruction that skips dealing with - * wildcard terms and variables */ - if (flecs_rule_term_fixed_id(&rule->filter, term)) { - if (op.kind == EcsRuleAnd) { - op.kind = EcsRuleAndId; +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; +} - /* 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; - ecs_write_flags_t write_state = ctx->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); - flecs_rule_compile_term_id(world, rule, &op, &term->second, - &op.second, EcsRuleSecond, EcsVarEntity, ctx); - - if (first_is_var) first_var = op.first.var; - if (second_is_var) second_var = op.second.var; +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); + } - /* Insert each instructions for table -> entity variable if needed */ - bool first_written = flecs_rule_compile_ensure_vars( - rule, &op, &op.first, EcsRuleFirst, ctx, cond_write); - bool second_written = flecs_rule_compile_ensure_vars( - rule, &op, &op.second, EcsRuleSecond, ctx, cond_write); + ecs_table_cache_init(world, &idr->cache); - /* Do src last, in case it uses the same variable as first/second */ - flecs_rule_compile_term_id(world, rule, &op, &term->src, - &op.src, EcsRuleSrc, EcsVarAny, ctx); - if (src_is_var) src_var = op.src.var; - bool src_written = flecs_rule_is_written(src_var, ctx->written); + idr->id = id; + idr->refcount = 1; + idr->reachable.current = -1; - /* Cache the current value of op.first. This value may get overwritten with - * a variable when term has component inheritance, but Not operations may - * need the original value to initialize the result id with. */ - ecs_rule_ref_t prev_first = op.first; - ecs_flags8_t prev_op_flags = op.flags; + bool is_wildcard = ecs_id_is_wildcard(id); + bool is_pair = ECS_IS_PAIR(id); - /* 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) { - 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); + 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); + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - /* Update write administration */ - src_written = true; - } - } + /* Relationship object can be 0, as tables without a ChildOf + * relationship are added to the (ChildOf, 0) id record */ + tgt = ECS_PAIR_SECOND(id); - /* 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); - src_var = op.src.var; +#ifdef FLECS_DEBUG + /* Check constraints */ + if (tgt) { + tgt = ecs_get_alive(world, tgt); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); } - } - - flecs_rule_compile_ensure_vars(rule, &op, &op.src, EcsRuleSrc, ctx, cond_write); + 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; - /* If source is Any (_) and first and/or second are unconstrained, insert an - * ids instruction instead of an And */ - if (term->flags & EcsTermMatchAnySrc) { - /* 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) */ - op.kind = EcsRuleIdsLeft; - } else { - /* If second is wildcard, traverse right: (r, *) -> */ - op.kind = EcsRuleIdsRight; + /* 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; } - op.src.entity = 0; - op.flags &= (ecs_flags8_t)~(EcsRuleIsVar << EcsRuleSrc); /* ids has no src */ - op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleSrc); } - } +#endif - /* 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); - } - } + 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 term has component inheritance enabled, insert instruction to walk - * down the relationship tree of the id. */ - if (term->flags & EcsTermIdInherited) { - if (is_not) { - /* Ensure that term only matches if none of the inherited ids match - * with the source. */ - flecs_rule_begin_none(ctx); - } - flecs_rule_insert_inheritance(rule, term, &op, ctx, cond_write); - } + /* If pair is not a wildcard, append it to wildcard lists. These + * allow for quickly enumerating all relationships for an object, + * or all objecs for a relationship. */ + flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); - /* If previous term was ScopeOpen with a Not operator, insert operation to - * ensure that none of the results inside the scope should match. */ - if (!first_term && term[-1].first.id == EcsScopeOpen) { - if (term[-1].oper == EcsNot) { - flecs_rule_begin_none(ctx); - flecs_rule_begin_not(ctx); - } - flecs_rule_compile_push(ctx); - } + idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); + flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); - /* Handle Not, Optional, Or operators */ - if (is_not) { - flecs_rule_begin_not(ctx); - } else if (term->oper == EcsOptional) { - flecs_rule_begin_option(ctx); - } else if (term->oper == EcsOr) { - if (first_term || term[-1].oper != EcsOr) { - if (!src_written) { - flecs_rule_begin_union(ctx); + if (rel == EcsUnion) { + idr->flags |= EcsIdUnion; } } + } else { + rel = id & ECS_COMPONENT_MASK; + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); } - /* Check if this term has variables that have been conditionally written, - * like variables written by an optional term. */ - if (ctx->cond_written) { - flecs_rule_begin_cond_eval(&op, ctx, cond_write_state); + /* 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; + } } - 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); - } + /* 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. */ - if (term->src.flags & EcsSelf) { - op.flags |= EcsRuleIsSelf; - } + /* 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); - if (builtin_pred) { - if (flecs_rule_compile_builtin_pred(term, &op, write_state)) { - return -1; + /* Add reference to (*, tgt) id record to entity record */ + tgt_r->idr = idr_t; } } - flecs_rule_op_insert(&op, ctx); + 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; - /* 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); - } + 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); } - /* 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); - } + /* Update counters */ + world->info.id_create_total ++; - /* Handle closing of conditional evaluation */ - if (ctx->cond_written && (first_is_var || second_is_var || src_is_var)) { - flecs_rule_end_cond_eval(&op, ctx); - } + if (!is_wildcard) { + world->info.id_count ++; - /* Handle closing of Not, Optional and Or operators */ - if (is_not) { - /* Restore original first id in case it got replaced with a variable */ - op.first = prev_first; - op.flags = prev_op_flags; - flecs_rule_end_not(&op, ctx, true); - } else if (term->oper == EcsOptional) { - flecs_rule_end_option(&op, ctx); - } else if (term->oper == EcsOr) { - if (ctx->cur->lbl_union != -1) { - flecs_rule_next_or(ctx); + if (idr->type_info) { + world->info.component_id_count ++; } else { - if (first_term || term[-1].oper != EcsOr) { - if (ctx->cur->lbl_union == -1) { - flecs_rule_begin_or(ctx); - } - } else if (term->oper == EcsOr) { - flecs_rule_next_or(ctx); - } + world->info.tag_id_count ++; } - } else if (term->oper == EcsAnd) { - if (!first_term && term[-1].oper == EcsOr) { - if (ctx->cur->lbl_union != -1) { - flecs_rule_end_union(ctx); - } else { - flecs_rule_end_or(ctx); - } + + if (is_pair) { + world->info.pair_id_count ++; } + } else { + world->info.wildcard_id_count ++; } - return 0; + return idr; +#ifdef FLECS_DEBUG +error: + return NULL; +#endif } -int flecs_rule_compile( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_rule_t *rule) +static +void flecs_id_record_assert_empty( + ecs_id_record_t *idr) { - 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_union = -1; - ctx.cur->lbl_prev = -1; - ctx.cur->lbl_not = -1; - ctx.cur->lbl_none = -1; - ecs_vec_clear(ctx.ops); - - /* Find all variables defined in query */ - flecs_rule_discover_vars(stage, rule); + (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); +} - /* If rule contains fixed source terms, insert operation to set sources */ - int32_t i, count = filter->term_count; - for (i = 0; i < 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; - } - } +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; - /* 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 < count; i ++) { - ecs_term_t *term = &terms[i]; - if (flecs_rule_term_fixed_id(filter, term)) { - ecs_rule_op_t set_ids = {0}; - set_ids.kind = EcsRuleSetIds; - flecs_rule_op_insert(&set_ids, &ctx); - break; - } - } + flecs_id_record_assert_empty(idr); - /* Compile query terms to instructions */ - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (flecs_rule_compile_term(world, rule, term, &ctx)) { - return -1; - } - } + /* 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 This variable has been written as entity, insert an operation to - * assign it to it.entities for consistency. */ - ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); - 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); - } + 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(); - /* 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 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(); } } - /* 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; + /* Update counters */ + world->info.id_delete_total ++; - for (i = 0; i < filter->field_count; i ++) { - ecs_var_id_t var_id = rule->src_vars[i]; - if (!var_id) { - continue; - } + if (!ecs_id_is_wildcard(id)) { + world->info.id_count --; - 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; - } - } - } + if (ECS_IS_PAIR(id)) { + world->info.pair_id_count --; } - /* 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); + if (idr->type_info) { + world->info.component_id_count --; + } else { + world->info.tag_id_count --; } + } else { + world->info.wildcard_id_count --; + } - for (i = 0; i < filter->field_count; i ++) { - ecs_var_id_t var_id = rule->src_vars[i]; - if (!var_id) { - continue; - } + /* 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); - if (rule->vars[var_id].kind == EcsVarTable) { - var_id = flecs_rule_find_var_id(rule, rule->vars[var_id].name, - EcsVarEntity); + 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 */ + } - /* Variables used as source that aren't This must be entities */ - ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - } + 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); + } +} - rule->src_vars[i] = var_id; - } +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; +} - /* If filter is empty, insert Nothing instruction */ - if (!rule->filter.term_count) { - ecs_rule_op_t nothing = {0}; - nothing.kind = EcsRuleNothing; - flecs_rule_op_insert(¬hing, &ctx); - } else { - /* 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); +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; } - 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); + 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 0; + return idr; } -#endif +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))); + } - /** - * @file addons/rules/api.c - * @brief User facing API for rules. - */ + return idr; +} -#include +void flecs_id_record_claim( + ecs_world_t *world, + ecs_id_record_t *idr) +{ + (void)world; + idr->refcount ++; +} -#ifdef FLECS_RULES +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); -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) + if (!rc) { + flecs_id_record_free(world, idr); } -}; -static -const char* flecs_rule_op_str( - uint16_t kind) -{ - switch(kind) { - case EcsRuleAnd: return "and "; - case EcsRuleAndId: return "and_id "; - case EcsRuleAndAny: return "andany "; - case EcsRuleWith: return "with "; - case EcsRuleTrav: return "trav "; - case EcsRuleIdsRight: return "idsr "; - case EcsRuleIdsLeft: return "idsl "; - case EcsRuleEach: return "each "; - case EcsRuleStore: return "store "; - case EcsRuleUnion: return "union "; - 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 EcsRuleSetVars: return "setvars "; - case EcsRuleSetThis: return "setthis "; - case EcsRuleSetFixed: return "setfix "; - case EcsRuleSetIds: return "setids "; - case EcsRuleContain: return "contain "; - case EcsRulePairEq: return "pair_eq "; - case EcsRuleSetCond: return "setcond "; - case EcsRuleJmpCondFalse: return "jfalse "; - case EcsRuleJmpNotSet: return "jnotset "; - case EcsRuleYield: return "yield "; - case EcsRuleNothing: return "nothing "; - default: return "!invalid"; - } + return rc; } -/* 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) +void flecs_id_record_release_tables( + ecs_world_t *world, + ecs_id_record_t *idr) { - ecs_poly_assert(poly, ecs_rule_t); + /* Cache should not contain tables that aren't empty */ + ecs_assert(flecs_table_cache_count(&idr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); - if (filter) { - iter[1] = ecs_rule_iter(world, (ecs_rule_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); - } else { - iter[0] = ecs_rule_iter(world, (ecs_rule_t*)poly); + 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); + } } } -static -void flecs_rule_fini( - ecs_rule_t *rule) +bool flecs_id_record_set_type_info( + ecs_world_t *world, + ecs_id_record_t *idr, + const ecs_type_info_t *ti) { - if (rule->vars != &rule->vars_cache.var) { - ecs_os_free(rule->vars); + 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 --; + } + } } - 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); + bool changed = idr->type_info != ti; + idr->type_info = ti; - ecs_poly_free(rule, ecs_rule_t); + 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; } -void ecs_rule_fini( - ecs_rule_t *rule) +ecs_hashmap_t* flecs_id_name_index_ensure( + ecs_world_t *world, + ecs_id_t id) { - 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_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_rule_t* ecs_rule_init( - ecs_world_t *world, - const ecs_filter_desc_t *const_desc) +ecs_hashmap_t* flecs_id_name_index_get( + const ecs_world_t *world, + ecs_id_t id) { - ecs_rule_t *result = ecs_poly_new(ecs_rule_t); - ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_poly_assert(world, ecs_world_t); - /* 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; + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; } - result->iterable.init = flecs_rule_iter_mixin_init; - - /* Compile filter to operations */ - if (flecs_rule_compile(world, stage, result)) { - goto error; - } + return idr->name_index; +} - ecs_entity_t entity = const_desc->entity; - result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; +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); - if (entity) { - EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t); - poly->poly = result; - ecs_poly_modified(world, entity, ecs_rule_t); + ecs_id_record_t* idr = flecs_id_record_get(world, id); + if (!idr) { + return NULL; } - return result; -error: - ecs_rule_fini(result); - return NULL; + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } -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) +ecs_table_record_t* flecs_id_record_get_table( + const ecs_id_record_t *idr, + const ecs_table_t *table) { - 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; + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } -char* ecs_rule_str_w_profile( - const ecs_rule_t *rule, - const ecs_iter_t *it) +void flecs_init_id_records( + ecs_world_t *world) { - ecs_poly_assert(rule, ecs_rule_t); + /* 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)); +} - 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); +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)); + } - 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 + 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_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_assert(ecs_map_count(&world->id_index_hi) == 0, + ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_append(&buf, "%*s", indent, ""); - ecs_strbuf_appendstr(&buf, flecs_rule_op_str(op->kind)); - ecs_strbuf_appendstr(&buf, " "); + ecs_map_fini(&world->id_index_hi); + ecs_os_free(world->id_index_lo); +} - int32_t written = ecs_strbuf_written(&buf); - for (int32_t j = 0; j < (10 - (written - start)); j ++) { - ecs_strbuf_appendch(&buf, ' '); - } +/** + * @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. + */ - if (op->kind == EcsRuleJmpCondFalse || op->kind == EcsRuleSetCond || - op->kind == EcsRuleJmpNotSet) - { - ecs_strbuf_appendint(&buf, op->other); - ecs_strbuf_appendch(&buf, ' '); - } - - hidden_chars = flecs_rule_op_ref_str(rule, &op->src, src_flags, &buf); - if (op->kind == EcsRuleUnion) { - indent ++; - } +/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as + * this can severly 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); + + ecs_assert(size == ecs_vec_size(&table->data.records), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(count == ecs_vec_count(&table->data.records), + ECS_INTERNAL_ERROR, NULL); - if (!first_flags && !second_flags) { - ecs_strbuf_appendstr(&buf, "\n"); - continue; - } + 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; - written = ecs_strbuf_written(&buf) - hidden_chars; - for (int32_t j = 0; j < (30 - (written - start)); j ++) { - ecs_strbuf_appendch(&buf, ' '); - } + ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_appendstr(&buf, "("); - flecs_rule_op_ref_str(rule, &op->first, first_flags, &buf); + if (table->column_count) { + int32_t column_count = table->column_count; + ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); - 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]"); - } - default: - break; - } - } + int32_t *column_map = table->column_map; + ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_appendch(&buf, ')'); + ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_appendch(&buf, '\n'); + 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); } -#ifdef FLECS_LOG - char *str = ecs_strbuf_get(&buf); - flecs_colorize_buf(str, true, &buf); - ecs_os_free(str); -#endif - return ecs_strbuf_get(&buf); -} + 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); + } + } -char* ecs_rule_str( - const ecs_rule_t *rule) -{ - return ecs_rule_str_w_profile(rule, 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); + } + } -const ecs_filter_t* ecs_rule_get_filter( - const ecs_rule_t *rule) -{ - return &rule->filter; + ecs_assert((table->_->traversable_count == 0) || + (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); } +#else +#define flecs_table_check_sanity(table) +#endif -const char* ecs_rule_parse_vars( - ecs_rule_t *rule, - ecs_iter_t *it, - const char *expr) +/* 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_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; + ecs_flags32_t flags = 0; - const char *name = NULL; - if (rule->filter.entity) { - name = ecs_get_name(rule->filter.world, rule->filter.entity); + if (ti->hooks.ctor) { + flags |= EcsTableHasCtors; } - - ptr = ecs_parse_ws_eol(ptr); - if (!ptr[0]) { - return ptr; + if (ti->hooks.on_add) { + flags |= EcsTableHasCtors; } - - if (ptr[0] == '(') { - paren = true; - ptr = ecs_parse_ws_eol(ptr + 1); - if (ptr[0] == ')') { - return ptr + 1; - } + 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; + } - do { - ptr = ecs_parse_ws_eol(ptr); - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } + return flags; +} - int var = ecs_rule_find_var(rule, token); - if (var == -1) { - ecs_parser_error(name, expr, (ptr - expr), - "unknown variable '%s'", token); - return NULL; - } +static +void flecs_table_init_columns( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_count) +{ + if (!column_count) { + return; + } - ptr = ecs_parse_ws_eol(ptr); - if (ptr[0] != ':') { - ecs_parser_error(name, expr, (ptr - expr), - "missing ':'"); - return NULL; - } + 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; - ptr = ecs_parse_ws_eol(ptr + 1); - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } + 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]; - 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; + 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; } - ecs_iter_set_var(it, var, val); + t2s[i] = cur; + s2t[cur] = i; + tr->column = flecs_ito(int16_t, cur); - ptr = ecs_parse_ws_eol(ptr); - if (ptr[0] == ')') { - if (!paren) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected closing parenthesis"); - return NULL; - } + columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); + columns[cur].id = ids[i]; + columns[cur].size = ti->size; - 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; + 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; } - break; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected , or end of string"); - return NULL; } - } while (true); - return ptr; -error: - return NULL; +#ifdef FLECS_DEBUG + ecs_vec_init(NULL, &columns[cur].data, ti->size, 0); +#endif + + table->flags |= flecs_type_info_flags(ti); + cur ++; + } } -#endif +/* 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); + ecs_vec_init_t(NULL, &storage->records, ecs_record_t*, 0); -/** - * @file addons/rules/trav_cache.c - * @brief Cache that stores the result of graph traversal. - */ + 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; -#ifdef FLECS_RULES + 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_rule_build_down_cache( +void flecs_table_init_flags( 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_table_t *table) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); - if (!idr) { - return; - } + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; - ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, - ecs_trav_elem_t); - elem->entity = entity; - elem->idr = idr; + 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; - 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; - } + 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; - int32_t i, count = ecs_table_count(table); - ecs_record_t **records = table->data.records.array; - ecs_entity_t *entities = table->data.entities.array; - for (i = 0; i < count; i ++) { - ecs_record_t *r = records[i]; - if (r->row & EcsEntityIsTraversable) { - flecs_rule_build_down_cache( - world, a, ctx, cache, trav, entities[i]); + 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_rule_build_up_cache( +void flecs_table_append_to_records( 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_vec_t *records, + ecs_id_t id, + int32_t column) { - ecs_id_t *ids = table->type.array; - int32_t i = tr->column, 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; + /* 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_record_t *r = flecs_entities_get_any(world, second); - if (r->table) { - const 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); - } + ecs_table_cache_insert(&idr->cache, table, &tr->hdr); + } else { + tr->count ++; } -} -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); + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } -void flecs_rule_get_down_cache( - const ecs_rule_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t entity) +/* Main table initialization function */ +void flecs_table_init( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *from) { - 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; - } -} + /* Make sure table->flags is initialized */ + flecs_table_init_flags(world, table); -void flecs_rule_get_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); + /* 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. */ - 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; - } + 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; } - const 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; - } + /* 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; - ecs_id_t id = table->type.array[tr->column]; + 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 */ - 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; + /* 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; + } } -} -#endif - -/** - * @file addons/rules/engine.c - * @brief Rules engine implementation. - */ + /* 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; -#ifdef FLECS_RULES + 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; + } -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; + dst_i += dst_id <= src_id; + src_i += dst_id >= src_id; } -} -static -ecs_rule_op_ctx_t* _flecs_op_ctx( - const ecs_rule_run_ctx_t *ctx) -{ - return &ctx->op_ctx[ctx->op_index]; -} + /* 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; + } -#define flecs_op_ctx(ctx, op_kind) (&_flecs_op_ctx(ctx)->is.op_kind) + /* 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; + } -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 }; + /* 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); } - 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; + /* 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); + } + } + } } - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range; - } + 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); - return (ecs_table_range_t){ 0 }; -} + 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; + } -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; - } + idr = p_idr->parent; /* (R, *) */ + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range.table; - } + 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; + } - return NULL; -} + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + tr->count ++; + } -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); - } -} + last_pair = dst_i; -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); + /* 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); } } - 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; + /* 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; } - 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; -} + /* 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; -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; -} + /* 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); -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 - }; -} + 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); + } -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; -} + /* Claim id record so it stays alive as long as the table exists */ + flecs_id_record_claim(world, idr); -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); + /* Initialize event flags */ + table->flags |= idr->flags & EcsIdEventMask; - 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_pair_first(ctx->world, id), ctx); - } else { - flecs_rule_var_set_entity(op, var, id, ctx); - } + /* Initialize column index (will be overwritten by init_columns) */ + tr->column = -1; + + if (idr->flags & EcsIdAlwaysOverride) { + table->flags |= EcsTableHasOverrides; } - } - 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_pair_second(ctx->world, id), ctx); + + if ((i < table->type.count) && (idr->type_info != NULL)) { + column_count ++; } } -} -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); + if (column_count) { + table->column_map = flecs_walloc_n(world, int32_t, + dst_count + column_count); } - return (ecs_table_range_t){0}; -} + table->column_count = flecs_ito(int16_t, column_count); + flecs_table_init_data(world, table); -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); + 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 + }); } - return 0; } +/* Unregister table from id records */ 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) +void flecs_table_records_unregister( + ecs_world_t *world, + ecs_table_t *table) { - 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; + 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; - 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; - } - } + 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; - if (flags_2nd & (EcsRuleIsVar | EcsRuleIsEntity)) { - return ecs_pair(first, second); - } else { - return ecs_get_alive(ctx->world, first); + ecs_table_cache_remove(cache, table_id, &tr->hdr); + flecs_id_record_release(world, (ecs_id_record_t*)cache); } -} -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); + 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 -int16_t flecs_rule_next_column( - ecs_table_t *table, - ecs_id_t id, - int32_t column) +void flecs_table_add_trigger_flags( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event) { - 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); + (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; } - return flecs_ito(int16_t, column); } +/* Invoke OnRemove observers for all entities in table. Useful during table + * deletion or when clearing entities from a table. */ static -void flecs_rule_it_set_column( - ecs_iter_t *it, - int32_t field_index, - int32_t column) +void flecs_table_notify_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - it->columns[field_index] = column + 1; - if (it->sources[field_index] != 0) { - it->columns[field_index] *= -1; + 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 -ecs_id_t flecs_rule_it_set_id( - ecs_iter_t *it, +void flecs_table_invoke_hook( + ecs_world_t *world, ecs_table_t *table, - int32_t field_index, - int32_t column) + ecs_iter_action_t callback, + ecs_entity_t event, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count) { - 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]; + 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_rule_set_match( - const ecs_rule_op_t *op, - ecs_table_t *table, - int32_t column, - const ecs_rule_run_ctx_t *ctx) +void flecs_table_invoke_ctor( + ecs_column_t *column, + int32_t row, + int32_t count) { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - int32_t field_index = op->field_index; - if (field_index == -1) { - return; + 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); } - - 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); } +/* Destruct components */ 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) +void flecs_table_invoke_dtor( + ecs_column_t *column, + int32_t row, + int32_t count) { - 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); + 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); } - flecs_rule_set_vars(op, matched, ctx); } +/* Run hooks that get invoked when component is added to entity */ 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) +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_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; - } - } + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; - } + if (construct) { + flecs_table_invoke_ctor(column, row, count); } - 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->column); - 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 --; + 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); } - - flecs_rule_set_match(op, table, op_ctx->column, ctx); - return true; } +/* Run hooks that get invoked when component is removed from entity */ static -bool flecs_rule_select( - const ecs_rule_op_t *op, - bool redo, - const ecs_rule_run_ctx_t *ctx) +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_id_t id = 0; - if (!redo) { - id = flecs_rule_op_get_id(op, ctx); + 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); } - return flecs_rule_select_w_id(op, redo, ctx, id); } +/* Destruct all components and/or delete all entities in table in range */ static -bool flecs_rule_with( - const ecs_rule_op_t *op, - bool redo, - const ecs_rule_run_ctx_t *ctx) +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) { - 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->column); - op_ctx->remaining = flecs_ito(int16_t, tr->count); - } else { - if (--op_ctx->remaining <= 0) { - return false; - } + /* Can't delete and not update the entity index */ + ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); - op_ctx->column = flecs_rule_next_column(table, idr->id, op_ctx->column); - ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); - } + int32_t ids_count = table->column_count; + ecs_record_t **records = data->records.array; + ecs_entity_t *entities = data->entities.array; + int32_t i, c, end = row + count; - flecs_rule_set_match(op, table, op_ctx->column, ctx); - return true; -} + (void)records; -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); + 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); } -} -static -bool flecs_rule_select_id( - 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_iter_t *it = ctx->it; - int8_t field = op->field_index; - ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Throw up a lock just to be sure */ + table->_->lock = true; - 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; + /* 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); } } - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; + /* Destruct components */ + for (c = 0; c < ids_count; c++) { + flecs_table_invoke_dtor(&data->columns[c], row, count); } - } - - 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; - flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); - flecs_rule_it_set_column(it, field, tr->column); - 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; - } + /* 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); + ecs_assert(!e || records[i] == flecs_entities_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); - 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 (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 + records[i]->table = NULL; + records[i]->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) */ + } + } - ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); - if (!table) { - return false; - } + table->_->lock = 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; - } - } + /* 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); + ecs_assert(!e || records[i] == flecs_entities_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; + 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_assert(!e || records[i] == flecs_entities_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + records[i]->table = NULL; + records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK; + (void)e; + } + } } - - flecs_rule_it_set_column(it, field, tr->column); - return true; } +/* Cleanup table storage */ static -bool flecs_rule_and_id( - const ecs_rule_op_t *op, - bool redo, - const ecs_rule_run_ctx_t *ctx) +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) { - 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); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); + + if (!data) { + return; } -} -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; + 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); + } + + /* Sanity check */ + ecs_assert(data->records.count == + data->entities.count, ECS_INTERNAL_ERROR, NULL); + + 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; } - 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; + 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; } - 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); + 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; } - int32_t field = op->field_index; - if (field != -1) { - ctx->it->ids[field] = flecs_rule_op_get_id(op, ctx); + ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); + ecs_vec_fini_t(&world->allocator, &data->records, ecs_record_t*); + + if (deactivate && count) { + flecs_table_set_empty(world, table); } - return result; + table->_->traversable_count = 0; + table->flags &= ~EcsTableHasTraversable; } -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) +/* 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) { - ecs_table_t *table = range->table; - ecs_entity_t *entities = table->data.entities.array; - int32_t count = range->count; - if (!count) { - count = ecs_table_count(table); - } + flecs_table_fini_data(world, table, data, false, false, false, false); +} - 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]; - } +/* 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); +} - flecs_rule_set_trav_match(op, -1, trav, second, ctx); - return 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); } -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) +/* Cleanup, run OnRemove, delete from entity index, deactivate table */ +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table) { - 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; + flecs_table_fini_data(world, table, &table->data, true, true, true, 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) +/* 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) { - if (redo) { - return false; /* If everything's fixed, can only have a single result */ - } + (void)world; + flecs_table_notify_on_remove(world, table, &table->data); +} - 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; +/* 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; - /* 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; + 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 + }); } } - flecs_rule_set_trav_match(op, column, trav, second, ctx); - return true; -} + 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(); + } -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); + world->info.empty_table_count -= (ecs_table_count(table) == 0); - 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; + /* 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 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); - } + if (!is_root) { + ecs_type_t ids = { + .array = table->type.array, + .count = table->type.count + }; - /* Entity is traversable, which means it could have a subtree */ - flecs_rule_get_down_cache(ctx, &trav_ctx->cache, trav, second); - trav_ctx->index = 0; + flecs_hashmap_remove_w_hash( + &world->store.table_map, &ids, ecs_table_t*, table->_->hash); + } - if (op->match_flags & EcsTermReflexive) { - trav_ctx->index = -1; - return flecs_rule_trav_unknown_src_reflexive( - op, ctx, trav, second); - } + 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_record_count -= table->_->record_count; + world->info.table_storage_count -= table->column_count; + world->info.table_delete_total ++; + + if (!table->column_count) { + world->info.tag_table_count --; } else { - if (!trav_ctx->cache.id) { - /* No traversal cache, which means this is a regular select */ - return flecs_rule_select(op, redo, ctx); - } + world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); } - if (trav_ctx->index == -1) { - redo = false; /* First result after handling reflexive relationship */ - trav_ctx->index = 0; - } + flecs_free_t(&world->allocator, ecs_table__t, table->_); - /* 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))) { - return true; - } - - redo = false; + 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); } - return false; + ecs_log_pop_2(); } -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) +/* 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) { - 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; - } + flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); +} - 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); +/* 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); +} - /* 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); +/* 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; } - - return true; } +/* Mark table column dirty. This usually happens as the result of a set + * operation, or iteration of a query with [out] fields. */ 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) +void flecs_table_mark_table_dirty( + ecs_world_t *world, + ecs_table_t *table, + int32_t index) { - 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); + (void)world; + if (table->dirty_state) { + table->dirty_state[index] ++; + } +} - if (!redo) { - flecs_rule_get_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); +/* 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; } - } else { - trav_ctx->index ++; - } - if (trav_ctx->yield_reflexive) { - if (flecs_rule_trav_yield_reflexive_src(op, ctx, &range, trav)) { - return true; + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr || tr->column == -1) { + return; } - trav_ctx->yield_reflexive = false; - trav_ctx->index = 0; - } - if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { - return false; + table->dirty_state[tr->column + 1] ++; } - - 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) +/* 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) { - 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"); - return false; - } 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); + 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 -bool flecs_rule_idsright( - const ecs_rule_op_t *op, - bool redo, - const ecs_rule_run_ctx_t *ctx) +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_rule_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; + ecs_table__t *dst_meta = dst_table->_; + ecs_table__t *src_meta = src_table->_; + if (!dst_meta && !src_meta) { + return; + } - 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; + 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); } - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; - } + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } - cur = op_ctx->cur = cur->first.next; - } else { - if (!op_ctx->cur) { - return false; + /* 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); } - - cur = op_ctx->cur = op_ctx->cur->first.next; } +} - if (!cur) { - return false; +/* 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; } - flecs_rule_set_vars(op, cur->id, ctx); + 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 (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; + if (!src_column_count && !dst_column_count) { + return; } - return true; -} + ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; + ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; -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; + ecs_type_t dst_type = dst_table->type; + ecs_type_t src_type = src_table->type; - 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; - } + int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; + int32_t offset_old = src_meta ? src_meta->bs_offset : 0; - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; - } + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; - cur = op_ctx->cur = cur->second.next; - } else { - if (!op_ctx->cur) { - return false; - } + 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]; - cur = op_ctx->cur = op_ctx->cur->second.next; - } + if (dst_id == src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_bitset_t *dst_bs = &dst_columns[i_new]; - if (!cur) { - return false; - } + flecs_bitset_ensure(dst_bs, dst_index + count); - flecs_rule_set_vars(op, cur->id, ctx); + 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 (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; + 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; } - return true; + /* 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 -bool flecs_rule_each( - const ecs_rule_op_t *op, - bool redo, - const ecs_rule_run_ctx_t *ctx) +void flecs_table_grow_column( + ecs_world_t *world, + ecs_column_t *column, + int32_t to_add, + int32_t dst_size, + bool construct) { - ecs_rule_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); - int32_t row; + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_range_t range = flecs_rule_var_get_range(op->first.var, ctx); - ecs_table_t *table = range.table; - if (!table) { - return false; - } + 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; - 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(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row < ecs_table_count(table), 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); - ecs_entity_t *entities = table->data.entities.array; - ecs_entity_t e; - do { - e = entities[row ++]; - - /* Exclude entities that are used as markers by rule engine */ - } while ((e == EcsWildcard) || (e == EcsAny) || - (e == EcsThis) || (e == EcsVariable)); + /* Create vector */ + ecs_vec_t dst; + ecs_vec_init(&world->allocator, &dst, size, dst_size); + dst.count = dst_count; - flecs_rule_var_set_entity(op, op->src.var, e, ctx); + void *src_buffer = column->data.array; + void *dst_buffer = dst.array; - return true; -} + /* Move (and construct) existing elements to new vector */ + move_ctor(dst_buffer, src_buffer, count, ti); -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; - } -} + if (construct) { + /* Construct new element(s) */ + result = ECS_ELEM(dst_buffer, size, count); + ctor(result, to_add, ti); + } -static -bool flecs_rule_union( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) -{ - if (!redo) { - ctx->jump = flecs_itolbl(ctx->op_index + 1); - return true; + /* Free old vector */ + ecs_vec_fini(&world->allocator, &column->data, size); + + column->data = dst; } else { - ecs_rule_lbl_t next = flecs_itolbl(ctx->prev_index + 1); - if (next == op->next) { - return false; + /* 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); } - ctx->jump = next; - return true; - } -} -static -bool flecs_rule_end( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) -{ - (void)op; + result = ecs_vec_grow(&world->allocator, &column->data, size, to_add); - ecs_rule_ctrlflow_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrlflow); - if (!redo) { - op_ctx->lbl = ctx->prev_index; - return true; - } else { - ctx->jump = op_ctx->lbl; - return true; + 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 -bool flecs_rule_not( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +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) { - if (redo) { - return false; - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t field = op->field_index; - if (field == -1) { - return true; - } + int32_t cur_count = flecs_table_data_count(data); + int32_t column_count = table->column_count; - ecs_iter_t *it = ctx->it; + /* Add record to record ptr array */ + ecs_vec_set_size_t(&world->allocator, &data->records, ecs_record_t*, size); + ecs_record_t **r = ecs_vec_last_t(&data->records, ecs_record_t*) + 1; + data->records.count += to_add; + if (data->records.size > size) { + size = data->records.size; + } - /* Not terms return no data */ - it->columns[field] = 0; + /* 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; + ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL); - /* Ignore variables written by Not operation */ - uint64_t *written = ctx->written; - uint64_t written_cur = written[ctx->op_index] = 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); + /* 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); + } + ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); - /* Overwrite id with cleared out variables */ - ecs_id_t id = flecs_rule_op_get_id(op, ctx); - if (id) { - it->ids[field] = id; + /* 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); } - /* 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); - } + 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); } - if (flags_2nd & EcsRuleIsVar) { - if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ - flecs_rule_var_reset(op->second.var, ctx); - } + + /* 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 term has entity src, set it because no other instruction might */ - if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { - it->sources[field] = op->src.entity; + /* 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 true; /* Flip result */ + /* Return index of first added entity */ + return cur_count; } +/* Append operation for tables that don't have any complex logic */ static -const char* flecs_rule_name_arg( - const ecs_rule_op_t *op, - ecs_rule_run_ctx_t *ctx) +void flecs_table_fast_append( + ecs_world_t *world, + ecs_column_t *columns, + int32_t count) { - int8_t term_index = op->term_index; - ecs_term_t *term = &ctx->rule->filter.terms[term_index]; - return term->second.name; + /* 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); + } } -static -bool flecs_rule_compare_range( - const ecs_table_range_t *l, - const ecs_table_range_t *r) +/* Append entity to table */ +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record, + bool construct, + bool on_add) { - if (l->table != r->table) { - return false; - } + 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); - 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; + 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; + + /* Add record ptr to array with record ptrs */ + ecs_record_t **r = ecs_vec_append_t(&world->allocator, + &data->records, ecs_record_t*); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + *r = record; + + /* If the table is monitored indicate that there has been a change */ + 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 */ } - } else { - /* Entire table is matched */ + return count; } - return true; -} + ecs_entity_t *entities = data->entities.array; -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; - } + /* 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; - 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; - return true; - } else { - ecs_table_range_t l = flecs_rule_get_range( - op, &op->first, EcsRuleFirst, ctx); + /* 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); - if (!flecs_rule_compare_range(&l, &r)) { - return false; + 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); } - ctx->vars[first_var].range.offset = r.offset; - ctx->vars[first_var].range.count = r.count; - return true; + 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 -bool flecs_rule_pred_eq( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +void flecs_table_fast_delete_last( + ecs_column_t *columns, + int32_t column_count) { - 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); + 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 -bool flecs_rule_pred_eq_name( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +void flecs_table_fast_delete( + ecs_column_t *columns, + int32_t column_count, + int32_t index) { - 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; + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vec_remove(&column->data, column->size, index); } - - 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) +/* Delete entity from table */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + int32_t index, + bool destruct) { - 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); + 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); - /* If tables don't match, neq always returns once */ - if (l.table != r.table) { - return true && !redo; - } + flecs_table_check_sanity(table); - 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); - } + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; - l_offset = l.offset; - l_count = l.count; + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); - /* Cache old value */ - op_ctx->range = l; - } else { - l_offset = op_ctx->range.offset; - l_count = op_ctx->range.count; - } + /* 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); - /* 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; - } + /* Move last record ptr to index */ + ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL); - /* 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; + ecs_record_t **records = data->records.array; + ecs_record_t *record_to_move = records[count]; + records[index] = record_to_move; + ecs_vec_remove_last(&data->records); - 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; + /* Update record of moved entity in entity index */ + if (index != count) { + 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); } + } - /* Return second slice */ - var->range.table = l.table; - var->range.offset = r_end; - var->range.count = l_end - r_end; + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); - /* 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; + /* If table is empty, deactivate it */ + if (!count) { + flecs_table_set_empty(world, table); } -} -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; + /* Destruct component data */ + ecs_column_t *columns = data->columns; + int32_t column_count = table->column_count; + int32_t i; - 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 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); } - if (!l.count) { - l.count = ecs_table_count(l.table); - } + flecs_table_check_sanity(table); + return; + } - op_ctx->range = l; - op_ctx->index = l.offset; - op_ctx->name_col = flecs_ito(int16_t, - ecs_table_get_index(ctx->world, l.table, - ecs_pair(ecs_id(EcsIdentifier), EcsName))); - if (op_ctx->name_col == -1) { - return is_neq; + /* 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); + } } - op_ctx->name_col = flecs_ito(int16_t, - l.table->storage_map[op_ctx->name_col]); - ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); + + flecs_table_fast_delete_last(columns, column_count); + + /* Not last element, move last element to deleted element & destruct */ } else { - if (op_ctx->name_col == -1) { - /* Table has no name */ - return false; - } + /* 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); - l = op_ctx->range; - } + 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); + } - const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].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; - } + 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); + } - if (!result) { - if (offset != -1) { - break; + ecs_vec_remove_last(&column->data); } } else { - if (offset == -1) { - offset = op_ctx->index; - } + flecs_table_fast_delete(columns, column_count, index); } } - if (offset == -1) { - ctx->vars[first_var].range = op_ctx->range; - return false; + /* 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); } - ctx->vars[first_var].range.offset = offset; - ctx->vars[first_var].range.count = (op_ctx->index - offset); - return true; -} + /* 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); + } -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); + flecs_table_check_sanity(table); } +/* Move operation for tables that don't have any complex logic */ static -bool flecs_rule_pred_neq_match( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +void flecs_table_fast_move( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index) { - return flecs_rule_pred_match(op, redo, ctx, true); -} + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; -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_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; - 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); -} + 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; -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; - } + 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); + } - ecs_table_range_t r = flecs_range_from_entity(e, ctx); - return flecs_rule_pred_neq_w_range(op, redo, ctx, r); + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } } -static -bool flecs_rule_setvars( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +/* 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) { - (void)op; + 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); - 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; + ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); - if (redo) { - return false; + 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; } - int32_t i; - for (i = 0; i < filter->field_count; i ++) { - ecs_var_id_t var_id = src_vars[i]; - if (!var_id) { - continue; - } + 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); - it->sources[i] = flecs_rule_var_get_entity(var_id, ctx); + /* 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; - int32_t column = it->columns[i]; - if (column > 0) { - it->columns[i] = -column; - } - } + /* 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); - return true; -} + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; -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]; + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; - if (!redo) { - /* Save values so we can restore them later */ - op_ctx->range = vars[0].range; + 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; - /* 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; - } -} + if (dst_id == src_id) { + int32_t size = dst_column->size; -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; + 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 (redo) { - return false; - } + 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; + } - 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; + 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); + } } - } - - 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; + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } - if (redo) { - return false; + 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); } - 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; + 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); } - return true; + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); } -/* 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) +/* 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) { - if (redo) { - return false; - } + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); - ecs_var_id_t src_id = op->src.var; - ecs_var_id_t first_id = op->first.var; + 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); - 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); + return result; } -/* 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) +/* Set allocated table size */ +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t size) { - if (redo) { - return false; - } + ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); - ecs_iter_t *it = ctx->it; - ecs_id_t id = it->ids[op->field_index]; - return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); + 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); + } } -static -bool flecs_rule_jmp_if_not( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +/* Shrink table storage to fit number of entities */ +bool flecs_table_shrink( + ecs_world_t *world, + ecs_table_t *table) { - if (!redo) { - flecs_op_ctx(ctx, cond)->cond = false; - return true; - } else { - if (!flecs_op_ctx(ctx, cond)->cond) { - ctx->jump = op->other; - } - return false; + 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); + ecs_vec_reclaim_t(&world->allocator, &data->records, ecs_record_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; } -static -bool flecs_rule_jmp_set_cond( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +/* Return number of entities in table */ +int32_t flecs_table_data_count( + const ecs_data_t *data) { - if (!redo) { - ctx->op_ctx[op->other].is.cond.cond = true; - return true; - } else { - return false; - } + return data ? data->entities.count : 0; } +/* Swap operation for switch (union relationship) columns */ static -bool flecs_rule_jmp_not_set( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +void flecs_table_swap_switch_columns( + ecs_table_t *table, + int32_t row_1, + int32_t row_2) { - if (!redo) { - ecs_var_t *vars = ctx->vars; - if (flecs_rule_ref_flags(op->flags, EcsRuleFirst) == EcsRuleIsVar) { - if (vars[op->first.var].entity == EcsWildcard) { - ctx->jump = op->other; - return true; - } - } - if (flecs_rule_ref_flags(op->flags, EcsRuleSecond) == EcsRuleIsVar) { - if (vars[op->second.var].entity == EcsWildcard) { - ctx->jump = op->other; - return true; - } - } - if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) == EcsRuleIsVar) { - if (vars[op->src.var].entity == EcsWildcard) { - ctx->jump = op->other; - return true; - } - } + int32_t i = 0, column_count = table->_->sw_count; + if (!column_count) { + return; + } - return true; - } else { - return false; + 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 -bool flecs_rule_run( - const ecs_rule_op_t *op, - bool redo, - ecs_rule_run_ctx_t *ctx) +void flecs_table_swap_bitset_columns( + ecs_table_t *table, + int32_t row_1, + int32_t row_2) { - 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 EcsRuleWith: return flecs_rule_with(op, redo, ctx); - case EcsRuleTrav: return flecs_rule_trav(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 EcsRuleUnion: return flecs_rule_union(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 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 EcsRuleContain: return flecs_rule_contain(op, redo, ctx); - case EcsRulePairEq: return flecs_rule_pair_eq(op, redo, ctx); - case EcsRuleJmpCondFalse: return flecs_rule_jmp_if_not(op, redo, ctx); - case EcsRuleSetCond: return flecs_rule_jmp_set_cond(op, redo, ctx); - case EcsRuleJmpNotSet: return flecs_rule_jmp_not_set(op, redo, ctx); - case EcsRuleYield: return false; - case EcsRuleNothing: return false; + 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); } - return false; } -static -void flecs_rule_iter_init( - ecs_rule_run_ctx_t *ctx) -{ - ecs_iter_t *it = ctx->it; - if (ctx->written) { - 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]; +/* 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; - if (!(it_written & (1ull << var_id)) || - (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) - { - continue; - } + 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); - /* 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 */ - } - } + flecs_table_check_sanity(table); + + if (row_1 == row_2) { + return; } - flecs_iter_validate(it); -} + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); -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); + ecs_entity_t *entities = table->data.entities.array; + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; - if (flecs_iter_next_row(it)) { - return true; - } + ecs_record_t **records = table->data.records.array; + ecs_record_t *record_ptr_1 = records[row_1]; + ecs_record_t *record_ptr_2 = records[row_2]; - return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); -error: - return false; -} + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); -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); + /* 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); - ecs_rule_iter_t *rit = &it->priv.iter.rule; - bool redo = it->flags & EcsIterIsValid; - ecs_rule_lbl_t next; + /* 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); + records[row_1] = record_ptr_2; + records[row_2] = record_ptr_1; - 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.prev_index = -1; - ctx.jump = -1; - ctx.op_ctx = rit->op_ctx; - const ecs_rule_op_t *ops = rit->ops; + flecs_table_swap_switch_columns(table, row_1, row_2); + flecs_table_swap_bitset_columns(table, row_1, row_2); - if (!(it->flags & EcsIterIsValid)) { - if (!ctx.rule) { - goto done; - } - flecs_rule_iter_init(&ctx); + ecs_column_t *columns = table->data.columns; + if (!columns) { + flecs_table_check_sanity(table); + return; } - do { - ctx.op_index = rit->op; - const ecs_rule_op_t *op = &ops[ctx.op_index]; + /* 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); + } -#ifdef FLECS_DEBUG - rit->profile[ctx.op_index].count[redo] ++; -#endif + void* tmp = ecs_os_alloca(temp_buffer_size); - bool result = flecs_rule_run(op, redo, &ctx); - ctx.prev_index = ctx.op_index; + /* Swap columns */ + for (i = 0; i < column_count; i ++) { + int32_t size = columns[i].size; + void *ptr = columns[i].data.array; - next = (&op->prev)[result]; - if (ctx.jump != -1) { - next = ctx.jump; - ctx.jump = -1; - } + void *el_1 = ECS_ELEM(ptr, size, row_1); + void *el_2 = ECS_ELEM(ptr, size, row_2); - if ((next > ctx.op_index)) { - ctx.written[next] |= ctx.written[ctx.op_index] | op->written; - } + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } - redo = next < ctx.prev_index; - rit->op = next; + flecs_table_check_sanity(table); +} - if (op->kind == EcsRuleYield) { - ecs_table_range_t *range = &rit->vars[0].range; - ecs_table_t *table = range->table; - if (table && !range->count) { - range->count = ecs_table_count(table); - } - flecs_iter_populate_data(ctx.world, it, range->table, range->offset, - range->count, it->ptrs); - return true; +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); } - } while (next >= 0); + ecs_vec_set_count(&world->allocator, + dst, size, dst_count + src_count); -done: - ecs_iter_fini(it); - return false; + 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_rule_iter_fini_ctx( - ecs_iter_t *it, - ecs_rule_iter_t *rit) +void flecs_table_merge_column( + ecs_world_t *world, + ecs_column_t *dst, + ecs_column_t *src, + int32_t column_size) { - 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); + ecs_size_t size = dst->size; + int32_t dst_count = dst->data.count; - 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; - default: - break; + 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_rule_iter_fini( - ecs_iter_t *it) +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) { - 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; + 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; -#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); + ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); + + if (!src_count) { + return; } - flecs_iter_free_n(rit->profile, ecs_rule_op_profile_t, op_count); -#endif + /* 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; - 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; -} + /* Merge record pointers */ + flecs_table_merge_vec(world, &dst_data->records, &src_data->records, + ECS_SIZEOF(ecs_record_t*), 0); -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); + 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 ++; + } + } - ecs_run_aperiodic(rule->filter.world, EcsAperiodicEmptyTables); + 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); - int32_t i, var_count = rule->var_count, op_count = rule->op_count; - it.world = (ecs_world_t*)world; - it.real_world = rule->filter.world; - 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; - flecs_filter_apply_iter_flags(&it, &rule->filter); + /* 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); + } - flecs_iter_init(world, &it, - flecs_iter_cache_ids | - flecs_iter_cache_columns | - flecs_iter_cache_sources | - flecs_iter_cache_ptrs); + /* 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); + } - rit->rule = rule; - rit->rule_vars = rule->vars; - rit->ops = rule->ops; - if (var_count) { - rit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); + /* 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 (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); + + /* If there is no data to merge, drop out */ + if (!src_data) { + return; } -#ifdef FLECS_DEBUG - rit->profile = flecs_iter_calloc_n(&it, ecs_rule_op_profile_t, op_count); -#endif + if (!dst_data) { + dst_data = &dst_table->data; + if (dst_table == src_table) { + move_data = true; + } + } - for (i = 0; i < var_count; i ++) { - rit->vars[i].entity = EcsWildcard; + 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; + ecs_record_t **src_records = src_data->records.array; + + /* 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; + if (dst_table != src_table) { + record = src_records[i]; + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + 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; } - it.variables = rit->vars; - it.variable_count = rule->var_pub_count; - it.variable_names = rule->var_names; + /* 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); + } -error: - return it; -} + if (src_count) { + if (!dst_count) { + flecs_table_set_empty(world, dst_table); + } + flecs_table_set_empty(world, src_table); -#endif + 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); + } -/** - * @file addons/doc.c - * @brief Doc addon. - */ + 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); -#ifdef FLECS_DOC + flecs_table_check_sanity(table); -static ECS_COPY(EcsDocDescription, dst, src, { - ecs_os_strset((char**)&dst->value, src->value); + 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); + } -static ECS_MOVE(EcsDocDescription, dst, src, { - ecs_os_free((char*)dst->value); - dst->value = src->value; - src->value = NULL; -}) + int32_t count = ecs_table_count(table); -static ECS_DTOR(EcsDocDescription, ptr, { - ecs_os_free((char*)ptr->value); -}) + if (!prev_count && count) { + flecs_table_set_empty(world, table); + } else if (prev_count && !count) { + flecs_table_set_empty(world, table); + } -void ecs_doc_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsName, { - .value = (char*)name - }); + flecs_table_check_sanity(table); } -void ecs_doc_set_brief( +/* Internal mechanism for propagating information to tables */ +void flecs_table_notify( ecs_world_t *world, - ecs_entity_t entity, - const char *description) + ecs_table_t *table, + ecs_table_event_t *event) { - ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { - .value = (char*)description - }); -} + if (world->flags & EcsWorldFini) { + return; + } -void ecs_doc_set_detail( - ecs_world_t *world, - ecs_entity_t entity, - const char *description) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { - .value = (char*)description - }); + switch(event->kind) { + case EcsTableTriggersForId: + flecs_table_add_trigger_flags(world, table, event->event); + break; + case EcsTableNoTriggersForId: + break; + } } -void ecs_doc_set_link( - ecs_world_t *world, - ecs_entity_t entity, - const char *link) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { - .value = (char*)link - }); -} +/* -- Public API -- */ -void ecs_doc_set_color( +void ecs_table_lock( ecs_world_t *world, - ecs_entity_t entity, - const char *color) + ecs_table_t *table) { - ecs_set_pair(world, entity, EcsDocDescription, EcsDocColor, { - .value = (char*)color - }); + if (table) { + if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->_->lock ++; + } + } } -const char* ecs_doc_get_name( - const ecs_world_t *world, - ecs_entity_t entity) +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table) { - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsName); - if (ptr) { - return ptr->value; - } else { - return ecs_get_name(world, entity); + 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 char* ecs_doc_get_brief( - const ecs_world_t *world, - ecs_entity_t entity) +const ecs_type_t* ecs_table_get_type( + const ecs_table_t *table) { - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocBrief); - if (ptr) { - return ptr->value; + if (table) { + return &table->type; } else { return NULL; } } -const char* ecs_doc_get_detail( +int32_t ecs_table_get_type_index( const ecs_world_t *world, - ecs_entity_t entity) + const ecs_table_t *table, + ecs_id_t id) { - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocDetail); - if (ptr) { - return ptr->value; - } else { - return NULL; + 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; } -} -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; + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return -1; } + + return tr->index; +error: + return -1; } -const char* ecs_doc_get_color( +int32_t ecs_table_get_column_index( const ecs_world_t *world, - ecs_entity_t entity) + const ecs_table_t *table, + ecs_id_t id) { - 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"); + 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); - 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_id_record_t *idr = flecs_id_record_get(world, id); + if (!idr) { + return -1; + } - ecs_set_hooks(world, EcsDocDescription, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsDocDescription), - .copy = ecs_copy(EcsDocDescription), - .dtor = ecs_dtor(EcsDocDescription) - }); + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return -1; + } - ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); + return tr->column; +error: + return -1; } -#endif - -/** - * @file addons/parser.c - * @brief Parser addon. - */ - - -#ifdef FLECS_PARSER - -#include - -#define ECS_ANNOTATION_LENGTH_MAX (16) - -#define TOK_NEWLINE '\n' -#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_WILDCARD '*' -#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_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]; +int32_t ecs_table_column_count( + const ecs_table_t *table) +{ + return table->column_count; +} -const char* ecs_parse_ws_eol( - const char *ptr) +int32_t ecs_table_type_to_column_index( + const ecs_table_t *table, + int32_t index) { - while (isspace(*ptr)) { - ptr ++; + 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; +} - return ptr; +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; } -const char* ecs_parse_ws( - const char *ptr) +void* ecs_table_get_column( + const ecs_table_t *table, + int32_t index, + int32_t offset) { - while ((*ptr != '\n') && isspace(*ptr)) { - ptr ++; + 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 ptr; + return result; +error: + return NULL; } -const char* ecs_parse_digit( - const char *ptr, - char *token) +void* ecs_table_get_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t offset) { - char *tptr = token; - char ch = ptr[0]; + 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); - if (!isdigit(ch) && ch != '-') { - ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); + world = ecs_get_world(world); + + int32_t index = ecs_table_get_column_index(world, table, id); + if (index == -1) { return NULL; } - tptr[0] = ch; - tptr ++; - ptr ++; - - for (; (ch = *ptr); ptr ++) { - if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { - break; - } + return ecs_table_get_column(table, index, offset); +error: + return NULL; +} - tptr[0] = ch; - tptr ++; - } +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); - tptr[0] = '\0'; - - return ptr; + return flecs_ito(size_t, table->data.columns[column].size); +error: + return 0; } -/* -- Private functions -- */ - -bool flecs_isident( - char ch) +int32_t ecs_table_count( + const ecs_table_t *table) { - return isalpha(ch) || (ch == '_'); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_table_data_count(&table->data); } -static -bool flecs_valid_identifier_start_char( - char ch) +bool ecs_table_has_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) { - if (ch && (flecs_isident(ch) || (ch == '*') || - (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) - { - return true; - } - - return false; + return ecs_table_get_type_index(world, table, id) != -1; } -static -bool flecs_valid_token_start_char( - char ch) +int32_t ecs_table_get_depth( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t rel) { - if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') - || (ch == '[') || (ch == ']') || (ch == '`') || - flecs_valid_identifier_start_char(ch)) - { - return true; - } + 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); - return false; + world = ecs_get_world(world); + + return flecs_relation_depth(world, rel, table); +error: + return -1; } -static -bool flecs_valid_token_char( - char ch) +bool ecs_table_has_flags( + ecs_table_t *table, + ecs_flags32_t flags) { - if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { - return true; - } - - return false; + return (table->flags & flags) == flags; } -static -bool flecs_valid_operator_char( - char ch) +int32_t flecs_table_column_to_union_index( + const ecs_table_t *table, + int32_t column) { - if (ch == TOK_OPTIONAL || ch == TOK_NOT) { - return true; + 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 false; + return -1; } -const char* ecs_parse_token( - const char *name, - const char *expr, - const char *ptr, - char *token_out, - char delim) +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2) { - 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; - } + flecs_table_swap(world, table, row_1, row_2); +} - tptr[0] = ch; - tptr ++; - ptr ++; +int32_t flecs_table_observed_count( + const ecs_table_t *table) +{ + return table->_->traversable_count; +} - if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { - tptr[0] = 0; - return ptr; - } +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; - int tmpl_nesting = 0; - bool in_str = ch == '"'; + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_column_t *column = &table->data.columns[index]; + ecs_size_t size = column->size; - 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; - } + ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, + ECS_INVALID_PARAMETER, NULL); - tptr[0] = ch; - tptr ++; - } + return ecs_vec_get(&column->data, size, ECS_RECORD_TO_ROW(r->row)); +error: + return NULL; +} - tptr[0] = '\0'; +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); - if (tmpl_nesting != 0) { - ecs_parser_error(name, expr, column, - "identifier '%s' has mismatching < > pairs", ptr); - return NULL; - } + world = ecs_get_world(world); - 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; + ecs_record_t *r = flecs_entities_get(world, entity); + if (r) { + return r; } - - return ptr; +error: + return NULL; } -const char* ecs_parse_identifier( - const char *name, - const char *expr, - const char *ptr, - char *token_out) +/** + * @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) { - if (!flecs_valid_identifier_start_char(ptr[0]) && (ptr[0] != '"')) { - ecs_parser_error(name, expr, (ptr - expr), - "expected start of identifier"); - return NULL; + 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; } - ptr = ecs_parse_token(name, expr, ptr, token_out, 0); + cache->empty_tables.count -= !!elem->empty; + cache->tables.count -= !elem->empty; - return ptr; + 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 -int flecs_parse_identifier( - const char *token, - ecs_term_id_t *out) +void flecs_table_cache_list_insert( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) { - 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 ++; + 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; } } - out->name = ecs_os_strdup(tptr); + elem->next = NULL; + elem->prev = last; - ecs_size_t len = ecs_os_strlen(out->name); - if (out->flags & EcsIsName) { - if (out->name[len - 1] != TOK_EXPR_STRING) { - ecs_parser_error(NULL, token, 0, "missing '\"' at end of string"); - return -1; - } else { - out->name[len - 1] = '\0'; - } + if (last) { + last->next = elem; } +} - return 0; +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); } -static -ecs_entity_t flecs_parse_role( - const char *name, - const char *sig, - int64_t column, - const char *token) +void ecs_table_cache_fini( + ecs_table_cache_t *cache) { - 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; - } + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_fini(&cache->index); } -static -ecs_oper_kind_t flecs_parse_operator( - char ch) +bool ecs_table_cache_is_empty( + const ecs_table_cache_t *cache) { - if (ch == TOK_OPTIONAL) { - return EcsOptional; - } else if (ch == TOK_NOT) { - return EcsNot; - } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } - return 0; + return ecs_map_count(&cache->index) == 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) +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result) { - char token[ECS_MAX_TOKEN_SIZE]; + 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); - ptr = ecs_parse_identifier(name, sig, ptr, token); - if (!ptr) { - return NULL; + bool empty; + if (!table) { + empty = false; + } else { + empty = ecs_table_count(table) == 0; } - 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; - } + result->cache = cache; + result->table = ECS_CONST_CAST(ecs_table_t*, table); + result->empty = empty; - ptr = ecs_parse_ws(ptr); + flecs_table_cache_list_insert(cache, result); - if (ptr[0] != TOK_BRACKET_CLOSE) { - ecs_parser_error(name, sig, column, "expected ]"); - return NULL; + if (table) { + ecs_map_insert_ptr(&cache->index, table->id, result); } - return ptr + 1; + ecs_assert(empty || cache->tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!empty || cache->empty_tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); } -static -uint8_t flecs_parse_set_token( - const char *token) +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem) { - 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_PARENT)) { - return EcsParent; - } else { - return 0; + 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; } -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) +void* ecs_table_cache_get( + const ecs_table_cache_t *cache, + const ecs_table_t *table) { - 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; + 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; } +} - do { - uint8_t tok = flecs_parse_set_token(token); - if (!tok) { - ecs_parser_error(name, expr, column, - "invalid set token '%s'", token); - return NULL; - } +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); - if (id->flags & tok) { - ecs_parser_error(name, expr, column, - "duplicate set token '%s'", token); - return NULL; - } - - id->flags |= tok; + ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); - if (ptr[0] == TOK_PAREN_OPEN) { - ptr ++; + flecs_table_cache_list_remove(cache, elem); + ecs_map_remove(&cache->index, table_id); - /* 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; - } + return elem; +} - id->trav = ecs_lookup_fullpath(world, token); - if (!id->trav) { - ecs_parser_error(name, expr, column, - "unresolved identifier '%s'", token); - return NULL; - } +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); - 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; - } - } + ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, + ecs_table_cache_hdr_t, table->id); + if (!elem) { + return false; + } - 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; - } - } - } + if (elem->empty == empty) { + return false; + } - /* 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; - } - } + flecs_table_cache_list_remove(cache, elem); + elem->empty = empty; + flecs_table_cache_list_insert(cache, elem); - /* End of set expression */ - } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { - break; - } - } while (true); + return true; +} - return ptr; +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; } -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) +bool flecs_table_cache_empty_iter( + ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) { - (void)column; + 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; +} - int32_t arg = 0; +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; +} - do { - if (flecs_valid_token_start_char(ptr[0])) { - if (arg == 2) { - ecs_parser_error(name, expr, (ptr - expr), - "too many arguments in term"); - return 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 false; + } + } - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } + it->cur = next; + it->next = next->next; + return next; +} - ecs_term_id_t *term_id = NULL; +/** + * @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]. + */ - if (arg == 0) { - term_id = &term->src; - } else if (arg == 1) { - term_id = &term->second; - } - /* 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); - return NULL; - } +/* Id sequence (type) utilities */ - ptr = ecs_parse_ws(ptr + 1); - ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, - NULL, term_id, TOK_PAREN_CLOSE); - if (!ptr) { - return NULL; - } +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)); +} - /* Check for term flags */ - } else if (!ecs_os_strcmp(token, TOK_CASCADE) || - !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) { - return NULL; - } +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; - /* Regular identifier */ - } else if (flecs_parse_identifier(token, term_id)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - return NULL; - } + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; - if (ptr[0] == TOK_AND) { - ptr = ecs_parse_ws(ptr + 1); + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); + } - term->id_flags = ECS_PAIR; + 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); + } - } else if (ptr[0] == TOK_PAREN_CLOSE) { - ptr = ecs_parse_ws(ptr + 1); - break; + return result; +} - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected ',' or ')'"); - return NULL; - } +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; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier or set expression"); - return NULL; + 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; +} - arg ++; +/* 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; - } while (true); + 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 ptr; + return -1; } +/* Count number of matching ids */ static -void flecs_parser_unexpected_char( - const char *name, - const char *expr, - const char *ptr, - char ch) +int flecs_type_count_matches( + const ecs_type_t *type, + ecs_id_t wildcard, + int32_t offset) { - 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"); + 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 -const char* flecs_parse_term( - const ecs_world_t *world, - const char *name, - const char *expr, - ecs_term_t *term_out) +int flecs_type_new_with( + ecs_world_t *world, + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t with) { - const char *ptr = expr; - char token[ECS_MAX_TOKEN_SIZE] = {0}; - ecs_term_t term = { .move = true /* parser never owns resources */ }; + ecs_id_t *src_array = src->array; + int32_t at = flecs_type_find_insert(src, 0, with); + if (at == -1) { + return -1; + } - ptr = ecs_parse_ws(ptr); + 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; - /* 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(ptr); + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } - if (flecs_valid_operator_char(ptr[0])) { - term.oper = flecs_parse_operator(ptr[0]); - ptr = ecs_parse_ws(ptr + 1); + int32_t remain = src->count - at; + if (remain) { + ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); } - /* 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; - } + dst_array[at] = with; - /* Is token a predicate? */ - if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_predicate; - } + return 0; +} - /* Next token must be a predicate */ - goto parse_predicate; +/* 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); + } - /* Pair with implicit subject */ - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; + 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 ++; + } + } - /* 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; + dst->count = w; + if (w != count) { + dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); + } - /* 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; + return 0; +} - /* Nothing else expected here */ - } else { - flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; +/* 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; } -flecs_parse_role: - term.id_flags = flecs_parse_role(name, expr, (ptr - expr), token); - if (!term.id_flags) { - goto error; + int32_t src_count = src->count; + if (src_count == 1) { + dst->array = NULL; + dst->count = 0; + return 0; } - 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 (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); + } - /* If not, it's a predicate */ - goto parse_predicate; + int32_t dst_count = src_count - count; + dst->count = dst_count; + if (!dst_count) { + dst->array = NULL; + return 0; + } - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier after role"); - goto error; + 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); } -parse_predicate: - if (flecs_parse_identifier(token, &term.first)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; + int32_t remain = dst_count - at; + if (remain) { + ecs_os_memcpy_n( + &dst_array[at], &src_array[at + count], ecs_id_t, remain); } - /* 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; - } + return 0; +} - ptr = ecs_parse_ws(ptr); +/* 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 }; + } - if (ptr[0] == TOK_AND || !ptr[0]) { - goto parse_done; - } + 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 + }; +} - if (ptr[0] != TOK_COLON) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected token '%c' after predicate set expression", ptr[0]); - goto error; - } +/* 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); + } +} - 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); +/* 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; } +} - if (ptr[0] == TOK_PAREN_OPEN) { - ptr ++; - 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); - } +/* Graph edge utilities */ - goto parse_done; +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); } +} - goto parse_done; +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); +} -parse_eq: - term.src = term.first; - term.first = (ecs_term_id_t){0}; - term.first.id = EcsPredEq; - goto parse_right_operand; +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 }; +} -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; +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; } - 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; - } + 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); +} - if (term.first.id == EcsPredMatch) { - if (token[0] == '"' && token[1] == '!') { - term.oper = EcsNot; - } - } +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); +} - if (flecs_parse_identifier(token, &term.second)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } +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); +} - term.src.flags &= ~EcsTraverseFlags; - term.src.flags |= EcsSelf; - term.inout = EcsInOutNone; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier"); - goto error; +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); } - goto parse_done; -parse_pair: - ptr = ecs_parse_identifier(name, expr, ptr + 1, token); - if (!ptr) { - goto error; + + 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 (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 (id < FLECS_HI_COMPONENT_ID) { + edge = &edges->lo[id]; + } else { + edge = flecs_bcalloc(&world->allocators.graph_edge); } - 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; + 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); } - - 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; + edge = &edges->lo[id]; } else { - flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; + edge = flecs_table_ensure_hi_edge(world, edges, id); } -parse_pair_predicate: - if (flecs_parse_identifier(token, &term.first)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } + return edge; +} - 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; - } +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; - 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; - } - } + /* Remove backref from destination table */ + ecs_graph_edge_hdr_t *next = edge->hdr.next; + ecs_graph_edge_hdr_t *prev = edge->hdr.prev; - if (ptr[0] == TOK_PAREN_CLOSE) { - ptr ++; - 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; + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; } -parse_pair_object: - if (flecs_parse_identifier(token, &term.second)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; + /* Remove data associated with edge */ + ecs_table_diff_t *diff = edge->diff; + if (diff) { + flecs_table_diff_free(world, diff); } - if (term.id_flags != 0) { - if (!ECS_HAS_ID_FLAG(term.id_flags, PAIR)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of role '%s' with pair", - ecs_id_flag_str(term.id_flags)); - goto error; - } + /* 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 { - term.id_flags = ECS_PAIR; + flecs_bfree(&world->allocators.graph_edge, edge); } +} - 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 +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 -bool flecs_is_valid_end_of_term( - const char *ptr) +void flecs_table_init_edges( + ecs_graph_edges_t *edges) { - 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] == ':') || /* inheritance (in plecs) */ - (ptr[0] == '=')) /* assignment (in plecs) */ - { - return true; - } - return false; + edges->lo = NULL; + edges->hi = NULL; } -char* ecs_parse_term( - const ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_term_t *term) +static +void flecs_table_init_node( + ecs_graph_node_t *node) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_table_init_edges(&node->add); + flecs_table_init_edges(&node->remove); +} - ecs_term_id_t *src = &term->src; +bool flecs_table_records_update_empty( + ecs_table_t *table) +{ + bool result = false; + bool is_empty = ecs_table_count(table) == 0; - 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 (char*)ptr; + 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); } - if (ptr == expr && !strcmp(expr, "0")) { - return (char*)&ptr[1]; - } + return result; +} - /* Parse next element */ - ptr = flecs_parse_term(world, name, ptr, term); - if (!ptr) { - goto error; - } +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; - /* Check for $() notation */ - if (term->first.name && !ecs_os_strcmp(term->first.name, "$")) { - if (term->src.name) { - ecs_os_free(term->first.name); - - term->first = term->src; + flecs_table_init_node(&table->node); - if (term->second.name) { - term->src = term->second; - } else { - term->src.id = EcsThis; - term->src.name = NULL; - term->src.flags |= EcsIsVariable; - } + flecs_table_init(world, table, prev); +} - term->second.name = ecs_os_strdup(term->first.name); - term->second.flags |= EcsIsVariable; - } +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); } - /* Post-parse consistency checks */ + ecs_log_push_2(); - /* 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; - } + /* Store table in table hashmap */ + *(ecs_table_t**)table_elem.value = result; - term->oper = EcsOr; - } + /* 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; - /* 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; - } + flecs_init_table(world, result, prev); + + /* Update counters */ + world->info.table_count ++; + world->info.table_record_count += result->_->record_count; + world->info.table_storage_count += result->column_count; + world->info.empty_table_count ++; + world->info.table_create_total ++; + + if (!result->column_count) { + world->info.tag_table_count ++; + } else { + world->info.trivial_table_count += !(result->flags & EcsTableIsComplex); } - /* 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; - } + ecs_log_pop_2(); - if (src->flags != 0) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of 0 with non-default subject"); - goto error; - } + return result; +} - src->flags = EcsIsEntity; - src->id = 0; - ecs_os_free(term->first.name); - term->first.name = NULL; - } +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); - /* 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; - } + int32_t id_count = type->count; + if (!id_count) { + return &world->store.root; } - /* 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; - } + 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 (src->name && !ecs_os_strcmp(src->name, "0")) { - src->id = 0; - src->flags = EcsIsEntity; - } + /* 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); - /* 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 we get here, the table has not been found, so create it. */ + if (own_type) { + return flecs_create_table(world, type, elem, prev); } - ptr = ecs_parse_ws(ptr); - - return (char*)ptr; -error: - if (term) { - ecs_term_fini(term); - } - return NULL; + ecs_type_t copy = flecs_type_copy(world, type); + return flecs_create_table(world, ©, elem, prev); } -#endif - -/** - * @file addons/meta.c - * @brief C utilities for meta addon. - */ - +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; +} -#ifdef FLECS_META_C +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; +} -#include +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; + } + } + } -#define ECS_META_IDENTIFIER_LENGTH (256) + ecs_type_t node_type = node->type; + ecs_type_t next_type = next->type; -#define ecs_meta_error(ctx, ptr, ...)\ - ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); + 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); -typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; + /* 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]; -typedef struct meta_parse_ctx_t { - const char *name; - const char *desc; -} meta_parse_ctx_t; + bool added = id_next < id_node; + bool removed = id_node < id_next; -typedef struct meta_type_t { - ecs_meta_token_t type; - ecs_meta_token_t params; - bool is_const; - bool is_ptr; -} meta_type_t; + trivial_edge &= !added || id_next == id; + trivial_edge &= !removed || id_node == id; -typedef struct meta_member_t { - meta_type_t type; - ecs_meta_token_t name; - int64_t count; - bool is_partial; -} meta_member_t; + added_count += added; + removed_count += removed; -typedef struct meta_constant_t { - ecs_meta_token_t name; - int64_t value; - bool is_value_set; -} meta_constant_t; + i_node += id_node <= id_next; + i_next += id_next <= id_node; + } -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; + added_count += next_count - i_next; + removed_count += node_count - i_node; -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; + trivial_edge &= (added_count + removed_count) <= 1 && + !ecs_id_is_wildcard(id); - while ((ch = *ptr)) { - if (ch == '(' || ch == '<') { - stack[sp] = ch; + if (trivial_edge) { + /* If edge is trivial there's no need to create a diff element for it */ + return; + } - 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; - } - } + ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; + int32_t added_offset = builder->added.count; + int32_t removed_offset = builder->removed.count; - ptr ++; + 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 (!sp) { - break; + 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); } - } - 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; + i_node += id_node <= id_next; + i_next += id_next <= id_node; } - *value_out = strtol(token, NULL, 0); + 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]); + } - return ecs_parse_ws_eol(ptr); -error: - return NULL; + 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 -const char* parse_c_identifier( - const char *ptr, - char *buff, - char *params, - meta_parse_ctx_t *ctx) +void flecs_add_overrides_for_base( + ecs_world_t *world, + ecs_type_t *dst_type, + ecs_id_t pair) { - 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'; + 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; } - /* 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; - } + ecs_id_t *ids = base_table->type.array; - 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; + 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; + } + } } - - 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; + 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]); + } } - - return ptr; -error: - return NULL; } static -const char * meta_open_scope( - const char *ptr, - meta_parse_ctx_t *ctx) +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) { - /* 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; - } + r = ecs_get_alive(world, r); - ptr ++; - ptr = ecs_parse_ws_eol(ptr); + /* 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; - /* Is this the end of the type definition? */ - if (!*ptr) { - ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); - goto error; - } + 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); + } - /* 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; + flecs_type_add(world, dst_type, a); + flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); } - 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) +ecs_table_t* flecs_find_table_with( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t with) { - ptr = meta_open_scope(ptr, ctx); - if (!ptr) { - return NULL; - } + ecs_ensure_id(world, with); - token->is_value_set = false; + ecs_id_record_t *idr = NULL; + ecs_entity_t r = 0, o = 0; - /* Parse token, constant identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - if (!ptr) { - return NULL; - } + 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; + } - ptr = ecs_parse_ws_eol(ptr); - if (!ptr) { - return NULL; + 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; } - /* Explicit value assignment */ - if (*ptr == '=') { - int64_t value = 0; - ptr = parse_c_digit(ptr + 1, &value); - token->value = value; - token->is_value_set = true; + /* 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 */ } - /* Expect a ',' or '}' */ - if (*ptr != ',' && *ptr != '}') { - ecs_meta_error(ctx, ptr, "missing , after enum constant"); - goto error; + 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 (*ptr == ',') { - return ptr + 1; - } else { - return ptr; + 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); } -error: - return NULL; + + return flecs_table_ensure(world, &dst_type, true, node); } static -const char* meta_parse_type( - const char *ptr, - meta_type_t *token, - meta_parse_ctx_t *ctx) +ecs_table_t* flecs_find_table_without( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t without) { - 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 (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); + } } - /* 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); + /* 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 */ } - /* Check if type is a pointer */ - ptr = ecs_parse_ws_eol(ptr); - if (*ptr == '*') { - token->is_ptr = true; - ptr ++; - } + return flecs_table_ensure(world, &dst_type, true, node); +} -done: - return ptr; -error: - return NULL; +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 -const char* meta_parse_member( - const char *ptr, - meta_member_t *token, - meta_parse_ctx_t *ctx) +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) { - ptr = meta_open_scope(ptr, ctx); - if (!ptr) { - return NULL; - } + flecs_table_init_edge(table, edge, id, to); - token->count = 1; - token->is_partial = false; + flecs_table_ensure_hi_edge(world, &table->node.add, id); - /* Parse member type */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - token->is_partial = true; - goto error; - } + 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; - if (!ptr[0]) { - return ptr; - } + edge->hdr.next = next; + if (next) { + next->prev = &edge->hdr; + } - /* Next token is the identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - if (!ptr) { - goto error; + flecs_compute_table_diff(world, table, to, edge, id); } +} - /* 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 == '[') { - array_start = (char*)ptr; /* safe, will not be modified */ - } - } +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); - 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; + flecs_table_ensure_hi_edge(world, &table->node.remove, id); - } else if (array_end - array_start == 0) { - ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); - goto error; - } + 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; - token->count = atoi(array_start + 1); + to_refs->prev = &edge->hdr; + edge->hdr.next = to_refs; - 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'; + edge->hdr.prev = prev; + if (prev) { + prev->next = &edge->hdr; } - } - /* Expect a ; */ - if (*ptr != ';') { - ecs_meta_error(ctx, ptr, "missing ; after member declaration"); - goto error; + flecs_compute_table_diff(world, table, to, edge, id); } +} - return ptr + 1; -error: - return NULL; +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 -int meta_parse_desc( - const char *ptr, - meta_params_t *token, - meta_parse_ctx_t *ctx) +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) { - token->is_key_value = false; - token->is_fixed_size = false; + ecs_table_t *to = flecs_find_table_with(world, node, id); + flecs_init_edge_for_add(world, node, edge, id, to); + return to; +} - ptr = ecs_parse_ws_eol(ptr); - if (*ptr != '(' && *ptr != '<') { - ecs_meta_error(ctx, ptr, - "expected '(' at start of collection definition"); - goto error; - } +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); - ptr ++; + node = node ? node : &world->store.root; - /* Parse type identifier */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - goto error; - } + /* 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); - ptr = ecs_parse_ws_eol(ptr); + 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 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; - } + 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); + } - token->count = value; - token->is_fixed_size = true; + if (node != to) { + if (edge->diff) { + *diff = *edge->diff; } 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; + diff->added.count = 0; + diff->removed.array = id_ptr; + diff->removed.count = 1; } } - if (*ptr != ')' && *ptr != '>') { - ecs_meta_error(ctx, ptr, - "expected ')' at end of collection definition"); - goto error; - } - - return 0; + return to; error: - return -1; + return NULL; } -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_table_t* flecs_table_traverse_add( ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) + ecs_table_t *node, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff) { - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - 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; - } + node = node ? node : &world->store.root; - if (!params.count) { - ecs_meta_error(ctx, params_decl, "invalid array size"); - goto error; - } + /* 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_entity_t element_type = ecs_lookup_symbol(world, params.type.type, true); - if (!element_type) { - ecs_meta_error(ctx, params_decl, "unknown element type '%s'", - params.type.type); - } + 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 (!e) { - e = ecs_new_id(world); + 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); } - ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, 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 ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); + return to; error: - return 0; + return NULL; } -static -ecs_entity_t meta_lookup_vector( +ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) + ecs_type_t *type) { - 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_poly_assert(world, ecs_world_t); + return flecs_table_ensure(world, type, false, NULL); +} - ecs_entity_t element_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); +void flecs_init_root_table( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); - if (!e) { - e = ecs_new_id(world); - } + 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); - return ecs_set(world, e, EcsVector, { element_type }); -error: - return 0; + /* 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; } -static -ecs_entity_t meta_lookup_bitmask( +void flecs_table_clear_edges( ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) + ecs_table_t *table) { - (void)e; + (void)world; + ecs_poly_assert(world, ecs_world_t); - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; + ecs_log_push_1(); - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; + 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)); } - if (params.is_key_value) { - ecs_meta_error(ctx, params_decl, - "unexpected key value parameters for bitmask"); - goto error; + it = ecs_map_iter(remove_hi); + while (ecs_map_next(&it)) { + flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } - if (params.is_fixed_size) { - ecs_meta_error(ctx, params_decl, - "unexpected size for bitmask"); - goto error; + /* 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)); } - ecs_entity_t bitmask_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); - ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); + /* 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)); + } -#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 + 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); + } - return bitmask_type; -error: - return 0; + 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(); } -static -ecs_entity_t meta_lookup( +/* Public convenience functions for traversing table graph */ +ecs_table_t* ecs_table_add_id( ecs_world_t *world, - meta_type_t *token, - const char *ptr, - int64_t count, - meta_parse_ctx_t *ctx) + ecs_table_t *table, + ecs_id_t id) { - 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; + ecs_table_diff_t diff; + return flecs_table_traverse_add(world, table, &id, &diff); +} - /* Parse vector type */ - if (!token->is_ptr) { - if (!ecs_os_strcmp(typename, "ecs_array")) { - type = meta_lookup_array(world, 0, token->params, ctx); +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); +} - } else if (!ecs_os_strcmp(typename, "ecs_vector") || - !ecs_os_strcmp(typename, "flecs::vector")) - { - type = meta_lookup_vector(world, 0, token->params, ctx); +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); +} - } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { - type = meta_lookup_bitmask(world, 0, token->params, ctx); +/** + * @file expr/deserialize.c + * @brief Deserialize flecs string format into (component) values. + */ - } else if (!ecs_os_strcmp(typename, "flecs::byte")) { - type = ecs_id(ecs_byte_t); +#include - } else if (!ecs_os_strcmp(typename, "char")) { - type = ecs_id(ecs_char_t); +#ifdef FLECS_EXPR - } else if (!ecs_os_strcmp(typename, "bool") || - !ecs_os_strcmp(typename, "_Bool")) - { - type = ecs_id(ecs_bool_t); +/* String deserializer for values & simple expressions */ - } 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); +/* 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; - } 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); +/* Used to track temporary values */ +#define EXPR_MAX_STACK_SIZE (256) - } 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); +typedef struct ecs_expr_value_t { + const ecs_type_info_t *ti; + void *ptr; +} ecs_expr_value_t; - } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { - type = ecs_id(ecs_entity_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; - } else if (!ecs_os_strcmp(typename, "char*")) { - type = ecs_id(ecs_string_t); - } else { - type = ecs_lookup_symbol(world, typename, 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"; - } +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); - type = ecs_lookup_symbol(world, typename, true); +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; } - if (count != 1) { - ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); - - type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); + const ecs_type_info_t *ti = idr->type_info; + if (!ti) { + return NULL; } - if (!type) { - ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); - goto error; + 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 type; -error: - return 0; + return result; } static -int meta_parse_struct( - ecs_world_t *world, - ecs_entity_t t, - const char *desc) +const char* flecs_str_to_expr_oper( + const char *str, + ecs_expr_oper_t *op) { - 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 - }); + 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; } - ecs_set_scope(world, old_scope); - - return 0; -error: - return -1; + *op = EcsExprOperUnknown; + return NULL; } -static -int meta_parse_constants( - ecs_world_t *world, - ecs_entity_t t, - const char *desc, - bool is_bitmask) +const char *ecs_parse_expr_token( + const char *name, + const char *expr, + const char *ptr, + char *token) { - 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); - - meta_parse_ctx_t ctx = { - .name = name, - .desc = ptr - }; + char *token_ptr = token; - meta_constant_t token; - int64_t last_value = 0; + if (ptr[0] == '/') { + char ch; + if (ptr[1] == '/') { + // Single line comment + for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} + return ptr; + } else if (ptr[1] == '*') { + // Multi line comment + for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { + if (ch == '*' && ptr[1] == '/') { + return ptr + 2; + } + } - ecs_entity_t old_scope = ecs_set_scope(world, t); + ecs_parser_error(name, expr, ptr - expr, + "missing */ for multiline comment"); + return NULL; + } + } - 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; + 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; } + } - 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}); + 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 { - ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, - {(ecs_u32_t)last_value}); + break; } - - 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); + return ptr; } 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) +const char* flecs_parse_multiline_string( + ecs_meta_cursor_t *cur, + const char *name, + const char *expr, + const char *ptr) { - 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; + /* Multiline string */ + ecs_strbuf_t str = ECS_STRBUF_INIT; + char ch; + while ((ch = ptr[0]) && (ch != '`')) { + if (ch == '\\' && ptr[1] == '`') { + ch = '`'; + ptr ++; } - break; - default: - break; + 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 0; + return ptr + 1; error: - return -1; + return NULL; } -#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) +bool flecs_parse_is_float( + const char *ptr) { - 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; - } + 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; } - } 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. */ - ecs_quit(world); - - if (result == 1) { - return 0; /* Normal exit */ - } else { - return result; /* Error code */ } + return false; } +/* Attempt to resolve variable dotexpression to value (foo.bar) */ static -int flecs_default_frame_action( +ecs_value_t flecs_dotresolve_var( ecs_world_t *world, - const ecs_app_desc_t *desc) + ecs_vars_t *vars, + char *token) { - 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; + char *dot = strchr(token, '.'); + if (!dot) { + return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; + } -/* Serve REST API from wasm image when running in emscripten */ -#ifdef ECS_TARGET_EM -#include + dot[0] = '\0'; -ecs_http_server_t *flecs_wasm_rest_server; + const ecs_expr_var_t *var = ecs_vars_lookup(vars, token); + if (!var) { + return (ecs_value_t){0}; + } -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); - } + 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) + }; } -#endif -int ecs_app_run( +static +int flecs_meta_call( ecs_world_t *world, - ecs_app_desc_t *desc) + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_meta_cursor_t *cur, + const char *function) { - ecs_app_desc = *desc; + ecs_entity_t type = ecs_meta_get_type(cur); + void *value_ptr = ecs_meta_get_ptr(cur); - /* 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_app_desc.target_fps != 0) { - ecs_set_target_fps(world, ecs_app_desc.target_fps); + 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; } - if (ecs_app_desc.threads) { - ecs_set_threads(world, ecs_app_desc.threads); + + *(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; } - } - /* 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 - } + 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; + } - /* Monitoring periodically collects statistics */ - if (desc->enable_monitor) { -#ifdef FLECS_MONITOR - ECS_IMPORT(world, FlecsMonitor); + 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_warn("cannot enable monitoring, MONITOR addon not available"); + ecs_parser_error(name, expr, ptr - expr, + "doc_name() is not available without FLECS_DOC addon"); + return -1; #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"); + } else { + ecs_parser_error(name, expr, ptr - expr, + "unknown function '%s'", function); return -1; } - run_action = callback; - return 0; } -int ecs_app_set_frame_action( - ecs_app_frame_action_t callback) +/* 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) { - if (frame_action != flecs_default_frame_action && frame_action != callback) { - ecs_err("frame action already set"); - return -1; + /* String literal */ + if (ptr[0] == '"' || ptr[0] == '`') { + if (input_type == ecs_id(ecs_char_t)) { + return input_type; + } + return ecs_id(ecs_string_t); } - frame_action = callback; - - return 0; -} - -#endif - -/** - * @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 EcsOnCreateTrigger = FLECS_HI_COMPONENT_ID + 42; -const ecs_entity_t EcsOnDeleteTrigger = FLECS_HI_COMPONENT_ID + 43; -const ecs_entity_t EcsOnDeleteObservable = FLECS_HI_COMPONENT_ID + 44; -const ecs_entity_t EcsOnComponentHooks = FLECS_HI_COMPONENT_ID + 45; -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; + /* 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); + } + } -/* 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; + /* 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); + } + } -/** Meta module component ids */ -const ecs_entity_t ecs_id(EcsMetaType) = FLECS_HI_COMPONENT_ID + 97; -const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = FLECS_HI_COMPONENT_ID + 98; -const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 99; -const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 100; -const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 101; -const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 102; -const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 103; -const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 104; -const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 105; -const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 106; -const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 107; -const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 108; -const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 109; -const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 110; + /* 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; + } -/* Doc module components */ -const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 111; -const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 112; -const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 113; -const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 114; -const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 115; + 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'; + } + } -/* REST module components */ -const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 116; + ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); + if (v.type) { + return v.type; + } -/* Default lookup path */ -static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", token); + return 0; + } + return var->value.type; + } -/* 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 + /* 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); + } + } -/* -- Private functions -- */ + /* Entity identifier */ + if (isalpha(ptr[0])) { + if (!input_type) { /* Identifier could also be enum/bitmask constant */ + return ecs_id(ecs_entity_t); + } + } -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 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 (ecs_poly_is(world, ecs_world_t)) { - return &world->stages[0]; + if (ptr[0] == '[') { + ecs_parser_error(name, expr, ptr - expr, + "unknown type for collection literal"); + return 0; + } - } else if (ecs_poly_is(world, ecs_stage_t)) { - return (ecs_stage_t*)world; + ecs_parser_error(name, expr, ptr - expr, "invalid expression"); } - - return NULL; + + return input_type; } -ecs_stage_t* flecs_stage_from_world( - ecs_world_t **world_ptr) +/* 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) { - 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]; + 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); + default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } +error: + return 0; +} - *world_ptr = ((ecs_stage_t*)world)->world; - return (ecs_stage_t*)world; +/** 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; } -ecs_world_t* flecs_suspend_readonly( - const ecs_world_t *stage_world, - ecs_suspend_readonly_state_t *state) +static +bool flecs_oper_valid_for_type( + ecs_entity_t type, + ecs_expr_oper_t op) { - ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); + 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); + return false; + } +} - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage_world); - ecs_poly_assert(world, ecs_world_t); +/** 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); +} - bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); - bool is_deferred = ecs_is_deferred(world); +static +int flecs_oper_precedence( + ecs_expr_oper_t left, + ecs_expr_oper_t right) +{ + return (left > right) - (left < right); +} - if (!is_readonly && !is_deferred) { - state->is_readonly = false; - state->is_deferred = false; - return world; +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_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); + ecs_value_t result; + result.type = type; + result.ptr = flecs_expr_value_new(stack, type); - state->is_readonly = is_readonly; - state->is_deferred = is_deferred; + if (value->ptr) { + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); + ecs_meta_set_value(&cur, value); + } - /* Silence readonly checks */ - world->flags &= ~EcsWorldReadonly; + *value = result; +} - /* Hack around safety checks (this ought to look ugly) */ - ecs_world_t *temp_world = world; - ecs_stage_t *stage = flecs_stage_from_world(&temp_world); - state->defer_count = stage->defer; - state->commands = stage->commands; - state->defer_stack = stage->defer_stack; - flecs_stack_init(&stage->defer_stack); - state->scope = stage->scope; - state->with = stage->with; - stage->defer = 0; - ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); - - return world; +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; } -void flecs_resume_readonly( +static +ecs_entity_t flecs_binary_expr_type( ecs_world_t *world, - ecs_suspend_readonly_state_t *state) + 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_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); + ecs_entity_t result_type = 0, operand_type = 0; - if (state->is_readonly || state->is_deferred) { - ecs_dbg_3("resuming readonly mode"); - - ecs_run_aperiodic(world, 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"); + } - /* 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->commands, ecs_cmd_t); - stage->commands = state->commands; - flecs_stack_fini(&stage->defer_stack); - stage->defer_stack = state->defer_stack; - stage->scope = state->scope; - stage->with = state->with; + /* 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; } -} -/* 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); + 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 (!world->monitors.is_dirty) { - return; + if (flecs_expr_op_is_equality(op)) { + ecs_parser_error(name, expr, ptr - expr, + "mismatching types in equality expression"); + return 0; } - world->monitors.is_dirty = false; + 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; + } - 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; - } + operand_type = flecs_promote_type(ltype, rtype); - m->is_dirty = false; +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); + } - 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 - }); - } + if (!result_type) { + result_type = operand_type; } + + *operand_type_out = operand_type; + return result_type; +error: + return 0; } -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t id) -{ - ecs_map_t *monitors = &world->monitors.monitors; +/* Macro's to let the compiler do the operations & conversion work for us */ - /* 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; - } +#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");\ } -} -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); +#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");\ + } - 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; -} +#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");\ + } -void flecs_monitor_unregister( +#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_entity_t id, - ecs_query_t *query) + 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) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + /* 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; + } - ecs_map_t *monitors = &world->monitors.monitors; - if (!ecs_map_is_init(monitors)) { - return; + if (!flecs_oper_valid_for_type(type, op)) { + ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); + goto error; } - ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); - if (!m) { - return; + 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); } - 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; - } + 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 (!count) { - ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); - ecs_map_remove_free(monitors, id); + 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); + } } - if (!ecs_map_count(monitors)) { - ecs_map_fini(monitors); - } + return 0; +error: + return -1; } static -void flecs_init_store( - ecs_world_t *world) +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_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); + 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; + } - /* Initialize table map */ - flecs_table_hashmap_init(world, &world->store.table_map); + ptr = ecs_parse_ws_eol(ptr); - /* Initialize one root table per stage */ - flecs_init_root_table(world); -} + ecs_value_t rvalue = {0}; + const char *rptr = flecs_parse_expr(world, stack, ptr, &rvalue, op, desc); + if (!rptr) { + return NULL; + } -static -void flecs_clean_tables( - ecs_world_t *world) -{ - int32_t i, count = flecs_sparse_count(&world->store.tables); + if (flecs_binary_expr_do(world, stack, name, expr, ptr, + lvalue, &rvalue, result, op)) + { + return NULL; + } - /* 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; + ptr = rptr; - for (i = 1; i < count; i ++) { - ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, - ecs_table_t, i); - flecs_table_release(world, t); - } + 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; + } + } - /* 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); - } + break; + } while (true); - /* Clear the root table */ - if (count) { - flecs_table_reset(world, &world->store.root); - } + return ptr; } static -void flecs_fini_roots(ecs_world_t *world) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); +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; + } - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + sep[0] = '\0'; + const char *function = sep + 1; - 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; + 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); + } - /* 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]); + do { + if (!function[0]) { + ecs_parser_error(name, expr, ptr - expr, + "missing function name for function call '%s'", token); + return NULL; + } - 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 */ + if (flecs_meta_call(world, stack, name, expr, ptr, cur, + function) != 0) + { + goto error; } - int32_t i, count = table->data.entities.count; - ecs_entity_t *entities = table->data.entities.array; + ecs_entity_t type = ecs_meta_get_type(cur); + value->ptr = ecs_meta_get_ptr(cur); + value->type = type; - /* Count backwards so that we're always deleting the last entity in the - * table which reduces moving components around */ - 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); + ptr += 2; + if (ptr[0] != '.') { + break; + } - ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(r->row); - if (!(flags & EcsEntityIsTarget)) { - continue; /* Filter out entities that aren't objects */ - } + ptr ++; + char *paren = strchr(ptr, '('); + if (!paren) { + break; + } - ecs_delete(world, entities[i]); + 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; } - } - flecs_defer_end(world, &world->stages[0]); -} + ecs_os_strncpy(token, ptr, len); + token[len] = '\0'; + function = token; -static -void flecs_fini_store(ecs_world_t *world) { - flecs_clean_tables(world); - flecs_sparse_fini(&world->store.tables); - flecs_table_release(world, &world->store.root); - flecs_entities_clear(world); - flecs_hashmap_fini(&world->store.table_map); + ptr = paren; + } while (true); - 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); + return ptr; +error: + return NULL; } -/* Implementation for iterable mixin */ static -bool flecs_world_iter_next( - ecs_iter_t *it) +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) { - 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_entity_t*)flecs_entities_ids(world); - it->count = flecs_entities_count(world); - flecs_iter_validate(it); - - return true; -} + 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; -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; + ptr = ecs_parse_ws_eol(ptr); - if (filter) { - iter[0] = ecs_term_iter(world, filter); - } else { - iter[0] = (ecs_iter_t){ - .world = (ecs_world_t*)world, - .real_world = (ecs_world_t*)ecs_get_world(world), - .next = flecs_world_iter_next - }; + /* 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); } -} -static -void flecs_world_allocators_init( - ecs_world_t *world) -{ - ecs_world_allocators_t *a = &world->allocators; + /* 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; + } - flecs_allocator_init(&world->allocator); + result.type = type; + if (type != value->type) { + result.ptr = flecs_expr_value_new(stack, type); + } else { + result.ptr = value->ptr; + } - ecs_map_params_init(&a->ptr, &world->allocator); - ecs_map_params_init(&a->query_table_list, &world->allocator); + cur = ecs_meta_cursor(world, result.type, result.ptr); + if (!cur.valid) { + return NULL; + } - 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); -} + cur.lookup_action = desc ? desc->lookup_action : NULL; + cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; + } -static -void flecs_world_allocators_fini( - ecs_world_t *world) -{ - ecs_world_allocators_t *a = &world->allocators; + /* 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; - ecs_map_params_fini(&a->ptr); - ecs_map_params_fini(&a->query_table_list); + 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; + } - 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); + /* 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; - flecs_allocator_fini(&world->allocator); -} + } else if (!ecs_os_strcmp(token, "{")) { + /* Parse nested value scope */ + ecs_entity_t scope_type = ecs_meta_get_type(&cur); -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(); -} + depth ++; /* Keep track of depth so we know when parsing is done */ + if (ecs_meta_push(&cur) != 0) { + goto error; + } -/* -- Public functions -- */ + 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; + } + } -ecs_world_t *ecs_mini(void) { -#ifdef FLECS_OS_API_IMPL - ecs_set_os_api_impl(); -#endif - ecs_os_init(); + else if (!ecs_os_strcmp(token, "}")) { + depth --; - ecs_trace("#[bold]bootstrapping world"); - ecs_log_push(); + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected ']'"); + return NULL; + } - ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } - if (!ecs_os_has_heap()) { - ecs_abort(ECS_MISSING_OS_API, NULL); - } + else if (!ecs_os_strcmp(token, "[")) { + /* Open collection value scope */ + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } - if (!ecs_os_has_threading()) { - ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); - } + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '{'"); + return NULL; + } + } - if (!ecs_os_has_time()) { - ecs_trace("time management not available"); - } + else if (!ecs_os_strcmp(token, "]")) { + depth --; - flecs_log_addons(); + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + return NULL; + } -#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 + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } -#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 + 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; + } - 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); + else if (!ecs_os_strcmp(token, ",")) { + /* Move to next field */ + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } - world->flags |= EcsWorldInit; + else if (!ecs_os_strcmp(token, "null")) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } - flecs_world_allocators_init(world); - ecs_allocator_t *a = &world->allocator; + is_lvalue = true; + } - 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; + else if (token[0] == '\"') { + /* Regular string */ + if (ecs_meta_set_string_literal(&cur, token) != 0) { + goto error; + } - 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*); + is_lvalue = true; + } - 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); + else if (!ecs_os_strcmp(token, "`")) { + /* Multiline string */ + if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { + goto error; + } - world->info.time_scale = 1.0; - if (ecs_os_has_time()) { - ecs_os_get_time(&world->world_start_time); - } + is_lvalue = true; - 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); + } 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; + } - flecs_bootstrap(world); + 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); + } - world->flags &= ~EcsWorldInit; + 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; - ecs_trace("world ready!"); - ecs_log_pop(); + } else { + const char *tptr = ecs_parse_ws(ptr); + for (; ptr != tptr; ptr ++) { + if (ptr[0] == '\n') { + newline = true; + } + } - return world; -} + 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; + } + } -ecs_world_t *ecs_init(void) { - ecs_world_t *world = ecs_mini(); + is_lvalue = true; + } -#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; -} + /* 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}; -#define ARG(short, long, action)\ - if (i < argc) {\ - if (argv[i][0] == '-') {\ - if (argv[i][1] == '-') {\ - if (long && !strcmp(&argv[i][2], long ? long : "")) {\ - action;\ - parsed = true;\ - }\ - } else {\ - if (short && argv[i][1] == short) {\ - action;\ - parsed = true;\ - }\ - }\ - }\ - } + 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; + } -ecs_world_t* ecs_init_w_args( - int argc, - char *argv[]) -{ - ecs_world_t *world = ecs_init(); + flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, + &rvalue, out, EcsMul); + } + unary_op = 0; + } - (void)argc; - (void) argv; + 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; + } -#ifdef FLECS_DOC - if (argc) { - char *app = argv[0]; - char *last_elem = strrchr(app, '/'); - if (!last_elem) { - last_elem = strrchr(app, '\\'); + /* 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 (last_elem) { - app = last_elem + 1; + + if (!depth) { + /* Reached the end of the root scope */ + break; } - ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); + + ptr = ecs_parse_ws_eol(ptr); } -#endif - return world; -} + if (!value->ptr) { + value->type = result.type; + value->ptr = flecs_expr_value_new(stack, result.type); + } -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; -} + if (value->ptr != result.ptr) { + cur = ecs_meta_cursor(world, value->type, value->ptr); + ecs_meta_set_value(&cur, &result); + } -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); + ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + return ptr; error: - return true; + return NULL; } -void flecs_notify_tables( +const char* ecs_parse_expr( ecs_world_t *world, - ecs_id_t id, - ecs_table_event_t *event) + const char *ptr, + ecs_value_t *value, + const ecs_parse_expr_desc_t *desc) { - 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; - } + /* 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); - ecs_table_cache_iter_t it; - const ecs_table_record_t *tr; + /* Parse expression */ + bool storage_provided = value->ptr != NULL; + ptr = flecs_parse_expr(world, &stack, ptr, value, EcsExprOperUnknown, desc); - 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); - } + /* 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); -void ecs_default_ctor( - void *ptr, - int32_t count, - const ecs_type_info_t *ti) -{ - ecs_os_memset(ptr, 0, ti->size * count); + return ptr; } -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); -} +#endif -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); -} +/** + * @file expr/serialize.c + * @brief Serialize (component) values to flecs string format. + */ -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); -} + +#ifdef FLECS_EXPR 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); -} +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 -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); -} +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 -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); -} +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 -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); +ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { + return kind - EcsOpPrimitive; } -void ecs_set_hooks_id( - ecs_world_t *world, - ecs_entity_t component, - const ecs_type_hooks_t *h) +/* 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) { - 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; + 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; } - - 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; - } + 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 { - /* 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; + 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 { - ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; + 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_zerocpy(str, out); } } + } else { + ecs_strbuf_appendlit(str, "null"); } + break; } - - 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; - } + case EcsEntity: { + ecs_entity_t e = *(const ecs_entity_t*)base; + if (!e) { + ecs_strbuf_appendch(str, '0'); } else { - if (h->dtor) { - ti->hooks.move_dtor = flecs_default_dtor; - } + ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str); } + break; } - -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; + default: + ecs_err("invalid primitive kind"); + return -1; } - 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; + return 0; } -/* Unset data in tables */ +/* Serialize enumeration */ static -void flecs_fini_unset_tables( - ecs_world_t *world) +int flecs_expr_ser_enum( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) { - ecs_sparse_t *tables = &world->store.tables; - int32_t i, count = flecs_sparse_count(tables); + const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); + ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); - 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); + 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; } -} -/* 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_strbuf_appendstr(str, ecs_get_name(world, c->constant)); - ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); + return 0; +error: + return -1; } -/* Cleanup remaining type info elements */ +/* Serialize bitmask */ 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( +int flecs_expr_ser_bitmask( 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_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) { - 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. After this point no more - * user code is executed. */ - 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(); + 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; - /* 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); + ecs_strbuf_list_push(str, "", "|"); - ecs_dbg_1("#[bold]cleanup world datastructures"); - 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(); + /* 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; + } + } - flecs_world_allocators_fini(world); + 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; + } - /* End of the world */ - ecs_poly_free(world, ecs_world_t); - ecs_os_fini(); + if (!count) { + ecs_strbuf_list_appendstr(str, "0"); + } - ecs_trace("world destroyed, bye!"); - ecs_log_pop(); + ecs_strbuf_list_pop(str, ""); return 0; +error: + return -1; } -bool ecs_is_fini( - const ecs_world_t *world) +/* 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_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return ECS_BIT_IS_SET(world->flags, EcsWorldFini); -} + ecs_strbuf_list_push(str, "[", ", "); -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); -} + const void *ptr = base; -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); -} + 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); + } -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); + ecs_strbuf_list_pop(str, "]"); - if (world->info.target_fps == (ecs_ftime_t)0 || enable) { - ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); - } -error: - return; + return 0; } -void ecs_measure_system_time( - ecs_world_t *world, - bool enable) +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) { - 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; -} + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); -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); + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_measure_frame_time(world, true); - world->info.target_fps = fps; -error: - return; + 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); } -void* ecs_get_context( - const ecs_world_t *world) +/* 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) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->context; -error: - return NULL; -} + const EcsArray *a = ecs_get(world, op->type, EcsArray); + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); -void ecs_set_context( - ecs_world_t *world, - void *context) -{ - ecs_poly_assert(world, ecs_world_t); - world->context = context; + return expr_ser_type_elements( + world, a->type, ptr, a->count, str, true); } -void ecs_set_entity_range( - ecs_world_t *world, - ecs_entity_t id_start, - ecs_entity_t id_end) +/* 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) { - 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; - } + const ecs_vec_t *value = base; + const EcsVector *v = ecs_get(world, op->type, EcsVector); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); - world->info.min_id = start; - world->info.max_id = end; -error: - return; -} + int32_t count = ecs_vec_count(value); + void *array = ecs_vec_first(value); -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; + /* Serialize contiguous buffer of vector */ + return expr_ser_type_elements(world, v->type, array, count, str, false); } -ecs_entity_t ecs_get_max_id( - const ecs_world_t *world) +/* 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) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return flecs_entities_max_id(world); -error: + 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 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; } -void ecs_set_entity_generation( - ecs_world_t *world, - ecs_entity_t entity_with_generation) +/* 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) { - 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); + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; - flecs_entities_set_generation(world, entity_with_generation); + if (in_array <= 0) { + if (op->name) { + ecs_strbuf_list_next(str); + ecs_strbuf_append(str, "%s: ", op->name); + } - 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; + 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 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; } -const ecs_type_info_t* flecs_type_info_get( +/* Iterate over the type ops of a type */ +static +int flecs_expr_ser_type( const ecs_world_t *world, - ecs_entity_t component) + const ecs_vec_t *v_ops, + const void *base, + ecs_strbuf_t *str, + bool is_expr) { - 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_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); } -ecs_type_info_t* flecs_type_info_ensure( - ecs_world_t *world, - ecs_entity_t component) +int ecs_ptr_to_expr_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) { - 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_type_info_t*)ti; + 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 (!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); - } - } + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) { + goto error; } - return ti_mut; + return 0; +error: + return -1; } -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) +char* ecs_ptr_to_expr( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) { - bool changed = false; - - flecs_entities_ensure(world, component); + ecs_strbuf_t str = ECS_STRBUF_INIT; - 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); - } + if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; } - /* 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; + return ecs_strbuf_get(&str); } -void flecs_type_info_fini( - ecs_type_info_t *ti) +int ecs_ptr_to_str_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) { - 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((char*)ti->name); - ti->name = NULL; + 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; } -} -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; + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) { + goto error; } - 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); - } + return 0; +error: + return -1; } -static -ecs_ftime_t flecs_insert_sleep( - ecs_world_t *world, - ecs_time_t *stop) +char* ecs_ptr_to_str( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) { - 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); + ecs_strbuf_t str = ECS_STRBUF_INIT; - if (world->info.target_fps == (ecs_ftime_t)0.0) { - return delta_time; + if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; } - 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 (sleep_time != 0) { - 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; + return ecs_strbuf_get(&str); } -static -ecs_ftime_t flecs_start_measure_frame( - ecs_world_t *world, - ecs_ftime_t user_delta_time) +int ecs_primitive_to_expr_buf( + const ecs_world_t *world, + ecs_primitive_kind_t kind, + const void *base, + ecs_strbuf_t *str) { - ecs_poly_assert(world, ecs_world_t); + return flecs_expr_ser_primitive(world, kind, base, str, true); +} - ecs_ftime_t delta_time = 0; +#endif - if ((world->flags & EcsWorldMeasureFrameTime) || (user_delta_time == 0)) { - 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 (world->info.target_fps != 0) { - 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 (delta_time == 0); +/** + * @file expr/utils.c + * @brief String parsing utilities. + */ - world->frame_start_time = t; - /* Keep track of total time passed in world */ - world->info.world_time_total_raw += (ecs_ftime_t)delta_time; - } +#ifdef FLECS_EXPR - return (ecs_ftime_t)delta_time; -} +#include -static -void flecs_stop_measure_frame( - ecs_world_t* world) +char* ecs_chresc( + char *out, + char in, + char delimiter) { - 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); + 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; + default: + if (in == delimiter) { + *bptr++ = '\\'; + *bptr = delimiter; + } else { + *bptr = in; + } + break; } + + *(++bptr) = '\0'; + + return bptr; } -ecs_ftime_t ecs_frame_begin( - ecs_world_t *world, - ecs_ftime_t user_delta_time) +const char* ecs_chrparse( + const char *in, + char *out) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); - ecs_check(user_delta_time != 0 || 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 (user_delta_time == 0) { - user_delta_time = delta_time; - } + const char *result = in + 1; + char ch; - world->info.delta_time_raw = user_delta_time; - world->info.delta_time = user_delta_time * world->info.time_scale; + if (in[0] == '\\') { + result ++; - /* Keep track of total scaled time passed in world */ - world->info.world_time_total += world->info.delta_time; + 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]; + } - ecs_run_aperiodic(world, 0); + if (out) { + *out = ch; + } - return world->info.delta_time; + return result; error: - return (ecs_ftime_t)0; + return NULL; } -void ecs_frame_end( - ecs_world_t *world) +ecs_size_t ecs_stresc( + char *out, + ecs_size_t n, + char delimiter, + const char *in) { - 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]); + 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++; + } + } } - flecs_stop_measure_frame(world); + if (bptr) { + while (written < n) { + *bptr = '\0'; + bptr++; + written++; + } + } + return written; error: - return; + return 0; } -const ecs_world_info_t* ecs_get_world_info( - const ecs_world_t *world) +char* ecs_astresc( + char delimiter, + const char *in) { - world = ecs_get_world(world); - return &world->info; -} + if (!in) { + return NULL; + } -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - flecs_table_release(world, table); + 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 -void flecs_process_empty_queries( - ecs_world_t *world) +const char* flecs_parse_var_name( + const char *ptr, + char *token_out) { - 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]); + char ch, *bptr = token_out; - 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); + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } - 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); - } - } + if (isalpha(ch) || isdigit(ch) || ch == '_') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + break; } } - flecs_defer_end(world, &world->stages[0]); + if (bptr == token_out) { + goto error; + } + + *bptr = '\0'; + + return ptr; +error: + return NULL; } -/** Walk over tables that had a state change which requires bookkeeping */ -void flecs_process_pending_tables( - const ecs_world_t *world_r) +static +const char* flecs_parse_interpolated_str( + const char *ptr, + char *token_out) { - ecs_poly_assert(world_r, ecs_world_t); + char ch, *bptr = token_out; - /* 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; - } + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } - /* Safe to cast, world is not readonly */ - ecs_world_t *world = (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; + if (ch == '\\') { + if (ptr[1] == '}') { + *bptr = '}'; + bptr ++; + ptr += 2; + continue; + } + } + + if (ch != '}') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + ptr ++; + break; + } } - /* 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 datastructures is - * more predictable. */ - int32_t i, count = flecs_sparse_count(world->pending_tables); - if (!count) { - return; + if (bptr == token_out) { + goto error; } - flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); + *bptr = '\0'; - do { - ecs_sparse_t *pending_tables = world->pending_tables; - world->pending_tables = world->pending_buffer; - world->pending_buffer = NULL; + return ptr; +error: + return 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]); +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 (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 */ + 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 --; + } - /* 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(); + 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; + } - flecs_emit(world, world, &(ecs_event_desc_t){ - .event = evt, - .table = table, - .ids = &table->type, - .observable = world, - .flags = EcsEventTableOnly - }); + 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; + } - ecs_log_pop_3(); - } - world->info.empty_table_count += (table_count == 0) * 2 - 1; + if (ecs_ptr_to_str_buf( + world, var->value.type, var->value.ptr, &result)) + { + goto error; } - } - flecs_sparse_clear(pending_tables); - ecs_defer_end(world); + 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; + } - world->pending_buffer = pending_tables; - } while ((count = flecs_sparse_count(world->pending_tables))); + 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; + } - flecs_journal_end(); -} + if (ecs_ptr_to_str_buf( + world, expr_result.type, expr_result.ptr, &result)) + { + goto error; + } -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); + ecs_value_free(world, expr_result.type, expr_result.ptr); - if (ecs_table_count(table)) { - table->_->generation = 0; + ptr --; + } else { + ecs_strbuf_appendch(&result, ch); + } } - flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, - (uint32_t)table->id)[0] = table; + return ecs_strbuf_get(&result); +error: + return NULL; } -bool ecs_id_in_use( - const ecs_world_t *world, - ecs_id_t id) +void ecs_iter_to_vars( + const ecs_iter_t *it, + ecs_vars_t *vars, + int offset) { - 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); -} + 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); -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); + /* 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]; + } } -} - -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; + /* 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; + } - if (time_budget_seconds != 0 || (ecs_should_log_1() && ecs_os_has_time())) { - ecs_time_measure(&start); - } + void *ptr = it->ptrs[i]; + if (!ptr) { + continue; + } - if (time_budget_seconds != 0) { - time_budget = true; - } + ptr = ECS_OFFSET(ptr, offset * size); - if (!id) { - id = EcsAny; /* Iterate all empty tables */ + 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; + } + } } - 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; + /* 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]; } } - - ecs_table_t *table = tr->hdr.table; - ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); - if (table->_->refcount > 1) { - /* Don't delete claimed tables */ - continue; - } - - if (table->type.count < min_id_count) { + if (!e_ptr) { continue; } - uint16_t gen = ++ table->_->generation; - if (delete_generation && (gen > delete_generation)) { - if (flecs_table_release(world, table)) { - delete_count ++; - } - } else if (clear_generation && (gen > clear_generation)) { - if (flecs_table_shrink(world, table)) { - clear_count ++; - } + 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; } } } -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; +error: + return; } +#endif + /** - * @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. + * @file expr/vars.c + * @brief Utilities for variable substitution in flecs string expressions. */ -void flecs_observable_init( - ecs_observable_t *observable) +#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_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; + 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; } -void flecs_observable_fini( - ecs_observable_t *observable) +static +void flecs_expr_var_scope_fini( + ecs_world_t *world, + ecs_expr_var_scope_t *scope) { - 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_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_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; + ecs_vec_fini_t(&world->allocator, &scope->vars, ecs_expr_var_t); + flecs_name_index_fini(&scope->var_index); +} - /* All triggers should've unregistered by now */ - ecs_assert(!ecs_map_is_init(&er->event_ids), - ECS_INTERNAL_ERROR, NULL); - } +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; +} - flecs_sparse_fini(&observable->events); +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)); } -ecs_event_record_t* flecs_event_record_get( - const ecs_observable_t *o, - ecs_entity_t event) +void ecs_vars_push( + ecs_vars_t *vars) { - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Builtin events*/ - if (event == EcsOnAdd) return (ecs_event_record_t*)&o->on_add; - else if (event == EcsOnRemove) return (ecs_event_record_t*)&o->on_remove; - else if (event == EcsOnSet) return (ecs_event_record_t*)&o->on_set; - else if (event == EcsUnSet) return (ecs_event_record_t*)&o->un_set; - else if (event == EcsWildcard) return (ecs_event_record_t*)&o->on_wildcard; + 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; +} - /* User events */ - return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); +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_event_record_t* flecs_event_record_ensure( - ecs_observable_t *o, - ecs_entity_t event) +ecs_expr_var_t* ecs_vars_declare( + ecs_vars_t *vars, + const char *name, + ecs_entity_t type) { - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + 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; - ecs_event_record_t *er = flecs_event_record_get(o, event); - if (er) { - return er; + if (flecs_name_index_find(var_index, name, 0, 0) != 0) { + ecs_err("variable %s redeclared", name); + goto error; } - er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event); - er->event = event; - return er; -} - -static -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); - 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; - } + 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_event_id_record_t* flecs_event_id_record_get( - const ecs_event_record_t *er, - ecs_id_t id) +ecs_expr_var_t* ecs_vars_declare_w_value( + ecs_vars_t *vars, + const char *name, + ecs_value_t *value) { - if (!er) { - return NULL; - } + 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 (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; + 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_event_id_record_t* flecs_event_id_record_get_if( - const ecs_event_record_t *er, - ecs_id_t id) +ecs_expr_var_t* flecs_vars_scope_lookup( + ecs_expr_var_scope_t *scope, + const char *name) { - ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); - if (!ider) { + 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; } - if (ider->observer_count) { - return ider; - } - - return NULL; + return ecs_vec_get_t(&scope->vars, ecs_expr_var_t, + flecs_uto(int32_t, var_id - 1)); } -ecs_event_id_record_t* flecs_event_id_record_ensure( - ecs_world_t *world, - ecs_event_record_t *er, - ecs_id_t id) +ecs_expr_var_t* ecs_vars_lookup( + const ecs_vars_t *vars, + const char *name) { - ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); - if (ider) { - return ider; - } + return flecs_vars_scope_lookup(vars->cur, name); +} - ider = ecs_os_calloc_t(ecs_event_id_record_t); +#endif - 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; - } +/** + * @file json/deserialize.c + * @brief Deserialize JSON strings into (component) values. + */ - ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); - ecs_map_insert_ptr(&er->event_ids, id, ider); - return ider; -} +/** + * @file json/json.h + * @brief Internal functions for JSON addon. + */ -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; - } +#ifdef FLECS_JSON - /* Populate array with observer sets matching the id */ - int32_t count = 0; - iders[0] = flecs_event_id_record_get_if(er, EcsAny); - count += iders[count] != 0; +/* Deserialize from JSON */ +typedef enum ecs_json_token_t { + JsonObjectOpen, + JsonObjectClose, + JsonArrayOpen, + JsonArrayClose, + JsonColon, + JsonComma, + JsonNumber, + JsonString, + JsonTrue, + JsonFalse, + JsonNull, + JsonLargeString, + JsonInvalid +} ecs_json_token_t; - iders[count] = flecs_event_id_record_get_if(er, id); - count += iders[count] != 0; +const char* flecs_json_parse( + const char *json, + ecs_json_token_t *token_kind, + char *token); - 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; - } +const char* flecs_json_parse_large_string( + const char *json, + ecs_strbuf_t *buf); - return count; -} +const char* flecs_json_expect( + const char *json, + ecs_json_token_t token_kind, + char *token, + const ecs_from_json_desc_t *desc); -bool flecs_observers_exist( - ecs_observable_t *observable, - ecs_id_t id, - ecs_entity_t event) -{ - ecs_event_record_t *er = flecs_event_record_get_if(observable, event); - if (!er) { - return false; - } +const char* flecs_json_expect_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); - return flecs_event_id_record_get_if(er, id) != NULL; -} +const char* flecs_json_expect_member_name( + const char *json, + char *token, + const char *member_name, + const ecs_from_json_desc_t *desc); -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_event_id_record_t **iders, - int32_t ider_count) -{ - ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); +const char* flecs_json_skip_object( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); - 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(); +const char* flecs_json_skip_array( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); - /* Propagate to records of traversable relationships */ - ecs_id_record_t *cur = tgt_idr; - while ((cur = cur->trav.next)) { - cur->reachable.generation ++; /* Invalidate cache */ +/* Serialize to JSON */ +void flecs_json_next( + ecs_strbuf_t *buf); - ecs_table_cache_iter_t idt; - if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { - continue; - } +void flecs_json_number( + ecs_strbuf_t *buf, + double value); - /* Get traversed relationship */ - ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); +void flecs_json_true( + ecs_strbuf_t *buf); - 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 (!ecs_table_count(table)) { - continue; - } +void flecs_json_false( + ecs_strbuf_t *buf); - bool owned = flecs_id_record_get_table(idr, table); +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value); - 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); - } +void flecs_json_array_push( + ecs_strbuf_t *buf); - /* Treat as new event as this could invoke observers again for - * different tables. */ - int32_t evtx = ++ world->event_id; +void flecs_json_array_pop( + ecs_strbuf_t *buf); - 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, evtx); +void flecs_json_object_push( + ecs_strbuf_t *buf); - if (!owned) { - /* Owned takes precedence */ - flecs_observers_invoke( - world, &ider->self_up, it, table, trav, evtx); - } - } +void flecs_json_object_pop( + ecs_strbuf_t *buf); - if (!table->_->traversable_count) { - continue; - } +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value); - ecs_record_t **records = ecs_vec_first(&table->data.records); - for (e = 0; e < entity_count; e ++) { - ecs_record_t *r = records[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, - iders, ider_count); - } - } - } +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); + +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind); + +#endif + +#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_log_pop_3(); + 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; } -static -void flecs_emit_propagate_invalidate_tables( - ecs_world_t *world, - ecs_id_record_t *tgt_idr) +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_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); + 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; - 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); + 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; } - /* 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; - } + if (desc) { + name = desc->name; + expr = desc->expr; + cur.lookup_action = desc->lookup_action; + cur.lookup_ctx = desc->lookup_ctx; + } - rc->generation ++; + 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; + } - ecs_table_cache_iter_t idt; - if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { - continue; + token = ecs_strbuf_get(&large_token); + token_kind = JsonString; } - 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; + if (token_kind == JsonObjectOpen) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; } - int32_t e, entity_count = ecs_table_count(table); - ecs_record_t **records = ecs_vec_first(&table->data.records); + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected '['"); + return NULL; + } + } else if (token_kind == JsonObjectClose) { + depth --; - for (e = 0; e < entity_count; e ++) { - ecs_id_record_t *idr_t = records[e]->idr; - if (idr_t) { - /* Only notify for entities that are used in pairs with - * traversable relationships */ - flecs_emit_propagate_invalidate_tables(world, idr_t); + 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 == 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; } - } -} -void flecs_emit_propagate_invalidate( - ecs_world_t *world, - ecs_table_t *table, - int32_t offset, - int32_t count) -{ - ecs_record_t **recs = ecs_vec_get_t(&table->data.records, - ecs_record_t*, offset); - int32_t i; - for (i = 0; i < count; i ++) { - ecs_record_t *record = recs[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; + if (token != token_buffer) { + ecs_os_free(token); + token = token_buffer; } - 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); + if (!depth) { + break; } } + + return json; +error: + return NULL; } -static -void flecs_override_copy( +const char* ecs_entity_from_json( 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) + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc_param) { - 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_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; } - 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); + json = flecs_json_expect(json, JsonObjectOpen, token, &desc); + if (!json) { + goto error; } -} -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; + lah = flecs_json_parse(json, &token_kind, token); + if (!lah) { + goto error; } - 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. */ - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - continue; - } + if (token_kind == JsonObjectClose) { + return lah; + } - int32_t column = tr->column; - column = ecs_table_type_to_storage_index(table, column); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + json = flecs_json_expect_member(json, token, &desc); + if (!json) { + return NULL; + } - const ecs_type_info_t *ti = idr->type_info; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_size_t size = ti->size; - ecs_vec_t *vec = &table->data.columns[column]; - return ecs_vec_get(vec, size, it->offset); + if (!ecs_os_strcmp(token, "path")) { + json = flecs_json_expect(json, JsonString, token, &desc); + if (!json) { + goto error; } - } - return NULL; -} + ecs_add_fullpath(world, e, token); -static -void flecs_emit_forward_up( - ecs_world_t *world, - ecs_event_record_t *er, - 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, - int32_t evtx); + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } -static -void flecs_emit_forward_id( - ecs_world_t *world, - ecs_event_record_t *er, - 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, - int32_t evtx) -{ - 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]; + if (token_kind == JsonObjectClose) { + return json; + } else if (token_kind != JsonComma) { + ecs_parser_error(name, expr, json - expr, "unexpected character"); + goto error; + } - /* 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); + 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; } - if (!may_override && (!ider_count && !ider_onset_count)) { - return; + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; } - it->ids[0] = id; - it->sources[0] = tgt; - it->event_id = id; - it->ptrs[0] = NULL; - it->sizes[0] = 0; + ids = json; - int32_t storage_i = ecs_table_type_to_storage_index(tgt_table, column); - if (storage_i != -1) { - ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_t *vec = &tgt_table->data.columns[storage_i]; - ecs_size_t size = idr->type_info->size; - it->ptrs[0] = ecs_vec_get(vec, size, offset); - it->sizes[0] = size; + json = flecs_json_skip_array(json, token, &desc); + if (!json) { + return NULL; } - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - bool owned = tr != 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; + } - 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, evtx); + json = flecs_json_expect_member_name(json, token, "values", &desc); + if (!json) { + goto error; + } - /* Owned takes precedence */ - if (!owned) { - flecs_observers_invoke(world, &ider->self_up, it, table, trav, evtx); + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; } + + values = json; } - /* Emit OnSet events for newly inherited components */ - if (storage_i != -1) { - bool override = false; + do { + ecs_entity_t first = 0, second = 0, type_id = 0; + ecs_id_t id; - /* 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; + ids = flecs_json_parse(ids, &token_kind, token); + if (!ids) { + goto error; } - if (ider_onset_count) { - it->event = er_onset->event; + 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; + } - 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, evtx); + break; + } else if (token_kind == JsonArrayOpen) { + ids = flecs_json_parse_path(world, ids, token, &first, &desc); + if (!ids) { + goto error; + } - /* Owned takes precedence */ - if (!owned) { - flecs_observers_invoke( - world, &ider->self_up, it, table, trav, evtx); - } else if (override) { - ecs_entity_t src = it->sources[0]; - it->sources[0] = 0; - flecs_observers_invoke(world, &ider->self, it, table, 0, evtx); - flecs_observers_invoke(world, &ider->self_up, it, table, 0, evtx); - it->sources[0] = src; + 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; } - it->event = event; - it->ptrs[0] = base_ptr; + 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; } - } -} -static -void flecs_emit_forward_and_cache_id( - ecs_world_t *world, - ecs_event_record_t *er, - 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, - int32_t evtx) -{ - /* 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); + 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; + } - flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, - tgt, tgt_table, column, offset, trav, evtx); -} + /* 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; + } -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); + if (values) { + ecs_from_json_desc_t parse_desc = { + .name = name, + .expr = expr, + }; - for (sp = 0; sp < stack_count; sp ++) { - ecs_table_t *elem = stack_elems[sp]; - if (flecs_id_record_get_table(idr, elem)) { - break; + 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 sp; + return flecs_json_expect(json, JsonObjectClose, token, &desc); +error: + return NULL; } static -bool flecs_emit_stack_has( - ecs_vec_t *stack, - ecs_id_record_t *idr) +ecs_entity_t flecs_json_new_id( + ecs_world_t *world, + ecs_entity_t ser_id) { - return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); + /* 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 -void flecs_emit_forward_cached_ids( +ecs_entity_t flecs_json_lookup( ecs_world_t *world, - ecs_event_record_t *er, - 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, - int32_t evtx) + ecs_entity_t parent, + const char *name, + const ecs_from_json_desc_t *desc) { - 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->column, - rc_offset, reachable_ids, trav, evtx); + 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_emit_dump_cache( - ecs_world_t *world, - const ecs_vec_t *vec) +void flecs_json_mark_reserved( + ecs_map_t *anonymous_ids, + ecs_entity_t e) { - 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"); + 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 -void flecs_emit_forward_table_up( +ecs_entity_t flecs_json_ensure_entity( ecs_world_t *world, - ecs_event_record_t *er, - 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, - int32_t evtx) + const char *name, + ecs_map_t *anonymous_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); - } + ecs_entity_t e = 0; - 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(); + 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); - /* 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; + /* 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); - 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; + /* Mark new id as reserved */ + flecs_json_mark_reserved(anonymous_ids, deser_id[0]); + } } - /* 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; + 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); + } + } - 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, evtx); - 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, evtx); - if (++i >= id_count) { - break; - } + return e; +} - id = ids[i]; - if (ECS_PAIR_FIRST(id) != trav) { - break; - } - } while (true); - } +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; - ecs_vec_remove_last(stack); - continue; + do { + ecs_id_t id = 0; + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; } - 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 + json = flecs_json_expect(json, JsonString, token, desc); + if (!json) { + goto error; } - /* Skip id if it's masked by a lower table in the tree */ - if (stack_at != stack_count) { - continue; + ecs_entity_t first = flecs_json_lookup(world, 0, token, desc); + if (!first) { + goto error; } - 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, evtx); - } + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + json = flecs_json_expect(json, JsonString, token, desc); + if (!json) { + goto error; + } - 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); + ecs_entity_t second = flecs_json_lookup(world, 0, token, desc); + if (!second) { + goto error; + } - /* Only add ids that were added for this table */ - int32_t count = ecs_vec_count(reachable_ids); - count -= rc_child_offset; + id = ecs_pair(first, second); - /* 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); + 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; } - 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); + table = ecs_table_add_id(world, table, id); + if (!table) { + goto error; } - } - - ecs_log_pop_3(); -} -static -void flecs_emit_forward_up( - ecs_world_t *world, - ecs_event_record_t *er, - 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, - int32_t evtx) -{ - 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; - } + 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]); - flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, - tgt, tgt_table, tgt_record, idr, stack, reachable_ids, evtx); + return table; +error: + return NULL; } static -void flecs_emit_forward( +int flecs_json_parse_entities( ecs_world_t *world, - ecs_event_record_t *er, - ecs_event_record_t *er_onset, - const ecs_type_t *emit_ids, - ecs_iter_t *it, + ecs_allocator_t *a, ecs_table_t *table, - ecs_id_record_t *idr, - int32_t evtx) + ecs_entity_t parent, + const char *json, + char *token, + ecs_vec_t *records, + const ecs_from_json_desc_t *desc) { - ecs_reachable_cache_t *rc = &idr->reachable; + char name_token[ECS_MAX_TOKEN_SIZE]; + ecs_json_token_t token_kind = 0; + ecs_vec_clear(records); - 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); + do { + json = flecs_json_parse(json, &token_kind, name_token); + if (!json) { + goto error; } - 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, evtx); - 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 ((token_kind != JsonNumber) && (token_kind != JsonString)) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected number or string"); + goto error; } - if (ecs_should_log_3()) { - ecs_dbg_3("cache after rebuild:"); - flecs_emit_dump_cache(world, &rc->ids); - } + 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); - 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); + 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_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); + ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); + ecs_record_t** elem = ecs_vec_append_t(a, records, ecs_record_t*); + *elem = r; - 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->column, offset, trav, evtx); + 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; } -/* 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( +static +const char* flecs_json_parse_column( ecs_world_t *world, - ecs_world_t *stage, - ecs_event_desc_t *desc) + ecs_table_t *table, + int32_t index, + const char *json, + char *token, + ecs_vec_t *records, + const ecs_from_json_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); + if (!table->column_count) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "table has no components"); + goto error; + } - ecs_time_t t = {0}; - bool measure_time = world->flags & EcsWorldMeasureSystemTime; - if (measure_time) { - ecs_time_measure(&t); + 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; } - const ecs_type_t *ids = desc->ids; - ecs_entity_t event = desc->event; - ecs_table_t *table = desc->table; - int32_t offset = desc->offset; - int32_t i, r, count = desc->count; - ecs_flags32_t table_flags = table->flags; + 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); - /* 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; + ecs_os_free(table_str); + goto error; } - /* 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; + 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; - 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; + do { + ecs_record_t *r = record_array[entity]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_assert(ecs_vec_get_t( + &table->data.records, ecs_record_t*, row)[0] == r, + ECS_INTERNAL_ERROR, NULL); - ecs_iter_t it = { - .world = stage, - .real_world = world, - .event = event, - .table = table, - .field_count = 1, - .ids = &ids_cache, - .ptrs = &ptrs_cache, - .sizes = &sizes_cache, - .columns = &columns_cache, - .sources = &sources_cache, - .other_table = desc->other_table, - .offset = offset, - .count = count, - .param = (void*)desc->param, - .flags = desc->flags | EcsIterIsValid - }; + void *ptr = ecs_vec_get(&column->data, size, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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; + json = ecs_ptr_from_json(world, type, ptr, json, desc); + if (!json) { + break; + } - ecs_observable_t *observable = ecs_get_observable(desc->observable); - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + 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; + } - /* 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. */ - ecs_event_record_t *er = flecs_event_record_get_if(observable, event); - ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); - ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); - ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet); + ecs_id_t *id_set = ecs_vec_append_t(a, columns_set, ecs_id_t); + *id_set = table->type.array[column]; - ecs_data_t *storage = NULL; - ecs_vec_t *columns = NULL; - if (count) { - storage = &table->data; - columns = storage->columns; - it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset); - } + 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; + } + } - int32_t id_count = ids->count; - ecs_id_t *id_array = ids->array; + 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]); - /* 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)); + /* Send OnSet notifications */ + ecs_defer_begin(world); + ecs_type_t type = { + .array = columns_set->array, + .count = columns_set->count }; - /* 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; + int32_t table_count = ecs_table_count(table); + int32_t i, record_count = ecs_vec_count(records); - /* Set if event has been propagated */ - bool propagated = false; + /* 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); + } + } - /* Does table has observed entities */ - bool has_observed = table_flags & EcsTableHasTraversable; + ecs_defer_end(world); - /* 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; + return json; +error: + return NULL; +} - ecs_event_id_record_t *iders[5] = {0}; - int32_t unset_count = 0; +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; -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; + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; + } - /* 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; + json = flecs_json_expect_member_name(json, token, "ids", desc); + if (!json) { + goto error; + } - if (is_pair && (idr_flags & EcsIdTraversable)) { - 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); + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } - /* 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; - } + ids = json; /* store start of ids array */ - /* 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; - } - } + json = flecs_json_skip_array(json, token, desc); + if (!json) { + goto error; + } - /* Forward events for components from pair target */ - flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr, evtx); - } + json = flecs_json_expect(json, JsonComma, token, desc); + if (!json) { + goto error; + } - 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 = ecs_table_type_to_storage_index( - base_table, 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]; - override_ptr = ecs_vec_get(base_v, ti->size, base_row); - } - } - } - } + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } - 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); + 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); - 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); + json = flecs_json_expect(json, JsonComma, token, desc); + if (!json) { + goto error; } - if (!ider_count && !override_ptr) { - /* If nothing more to do for this id, early out */ - continue; + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; } + } - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - const 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; - } + if (ecs_os_strcmp(token, "entities")) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected 'entities'"); + goto error; + } - int32_t column = tr->column, storage_i = -1; - it.columns[0] = column + 1; - it.ptrs[0] = NULL; - it.sizes[0] = 0; - it.event_id = id; - it.ids[0] = id; + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } - if (count) { - storage_i = ecs_table_type_to_storage_index(table, column); - if (storage_i != -1) { - /* If this is a component, fetch pointer & size */ - ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_t *vec = &columns[storage_i]; - ecs_size_t size = idr->type_info->size; - void *ptr = ecs_vec_get(vec, size, offset); - it.sizes[0] = size; + entities = json; /* store start of entity id array */ - 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, evtx); - flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA, evtx); - } - it.sources[0] = 0; - } - } - } + json = flecs_json_skip_array(json, token, desc); + if (!json) { + goto error; + } - it.ptrs[0] = ptr; - } else { - if (it.event == EcsUnSet) { - /* Only valid for components, not tags */ - continue; - } - } + 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; } - /* 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, evtx); - flecs_observers_invoke(world, &ider->self_up, &it, table, 0, evtx); + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; } - if (!ider_count || !count || !has_observed) { - continue; - } + 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; + } - /* If event is propagated, we don't have to manually invalidate entities - * lower in the tree(s). */ - propagated = true; + /* Find table from ids */ + ecs_table_t *table = flecs_json_parse_table(world, ids, token, desc); + if (!table) { + goto error; + } - /* 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; + /* Add entities to table */ + if (flecs_json_parse_entities(world, a, table, parent, + entities, token, records, desc)) + { + goto error; + } - ecs_record_t **recs = ecs_vec_get_t(&storage->records, - ecs_record_t*, offset); - for (r = 0; r < count; r ++) { - ecs_record_t *record = recs[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; - } + /* Parse values */ + if (values) { + json = flecs_json_parse_values(world, table, values, token, + records, columns_set, desc); + if (!json) { + goto error; + } - 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, iders, ider_count); - } + json = flecs_json_expect(json, JsonObjectClose, token, desc); + if (!json) { + goto error; } + } - it.table = table; - it.entities = entities; - it.count = count; - it.offset = offset; - it.sources[0] = 0; + 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 (count && can_forward && has_observed && !propagated) { - flecs_emit_propagate_invalidate(world, table, offset, count); + 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; } - can_override = false; /* Don't override twice */ - can_unset = false; /* Don't unset twice */ - can_forward = false; /* Don't forward twice */ + json = flecs_json_expect(json, JsonObjectOpen, token, &desc); + if (!json) { + goto error; + } - 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; + json = flecs_json_expect_member_name(json, token, "results", &desc); + if (!json) { + goto error; } - if (wcer && er != wcer) { - /* Repeat event loop for Wildcard event */ - er = wcer; - it.event = event; - goto repeat_event; + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; } -error: - if (measure_time) { - world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + json = lah; + goto end; } - return; -} -void ecs_emit( - ecs_world_t *stage, - ecs_event_desc_t *desc) -{ - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); - 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; + do { + json = flecs_json_parse_result(world, a, json, token, + &records, &columns_set, &desc); + if (!json) { + goto error; } - desc->table = table; - desc->offset = ECS_RECORD_TO_ROW(r->row); - desc->count = 1; - } - if (!desc->observable) { - desc->observable = world; - } - flecs_emit(world, stage, desc); -} -/** - * @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. - */ + 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]); -#include +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); -ecs_filter_t ECS_FILTER_INIT = { .hdr = { .magic = ecs_filter_t_magic }}; + json = flecs_json_expect(json, JsonObjectClose, token, &desc); + if (!json) { + goto error; + } -/* 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; + 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); -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); + return NULL; +} -static -void flecs_filter_error( - const ecs_filter_finalize_ctx_t *ctx, - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); +#endif - int32_t term_start = 0; +/** + * @file json/json.c + * @brief JSON serializer utilities. + */ - 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); +#include - va_end(args); -} +#ifdef FLECS_JSON static -int flecs_term_id_finalize_flags( - ecs_term_id_t *term_id, - ecs_filter_finalize_ctx_t *ctx) +const char* flecs_json_token_str( + ecs_json_token_t token_kind) { - if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { - flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); - return -1; + 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 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 "<>"; +} - 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; +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 (term_id->flags & EcsParent) { - term_id->flags |= EcsUp; - term_id->trav = EcsChildOf; - } + 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; + } - if ((term_id->flags & EcsCascade) && !(term_id->flags & (EcsUp|EcsDown))) { - term_id->flags |= EcsUp; - } + json = ecs_chrparse(json, token_ptr ++); + } - if ((term_id->flags & (EcsUp|EcsDown)) && !term_id->trav) { - term_id->trav = EcsIsA; - } + if (!ch) { + token_kind[0] = JsonInvalid; + return NULL; + } else { + token_kind[0] = JsonString; + return json; + } + } else if (isdigit(ch) || (ch == '-')) { + token_kind[0] = JsonNumber; + return ecs_parse_digit(json, token); + } 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 (term_id->trav && !(term_id->flags & EcsTraverseFlags)) { - term_id->flags |= EcsUp; - } + if (isalpha(json[0]) || isdigit(json[0])) { + token_kind[0] = JsonInvalid; + return NULL; + } - return 0; + return json; + } else { + token_kind[0] = JsonInvalid; + return NULL; + } } -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* flecs_json_parse_large_string( + const char *json, + ecs_strbuf_t *buf) { - char *name = term_id->name; - if (!name) { - return 0; + if (json[0] != '"') { + return NULL; /* can only parse strings */ } - if (term_id->flags & EcsIsVariable) { - if (!ecs_os_strcmp(name, "This") || !ecs_os_strcmp(name, "this")) { - term_id->id = EcsThis; - if (free_name) { - ecs_os_free(term_id->name); - } - term_id->name = NULL; + char ch, ch_out; + json ++; + for (; (ch = json[0]); ) { + if (ch == '"') { + json ++; + break; } - return 0; - } else if (term_id->flags & EcsIsName) { - return 0; - } - ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + json = ecs_chrparse(json, &ch_out); + ecs_strbuf_appendch(buf, ch_out); + } - 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; + if (!ch) { + return NULL; + } else { + return json; } +} - ecs_entity_t e = ecs_lookup_symbol(world, name, true); - if (scope && !e) { - e = ecs_lookup_child(world, scope, name); +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; +} - 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; - } +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; +} - 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; +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; +} - term_id->id = e; +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; - if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || - !ecs_os_strcmp(name, "$")) - { - term_id->flags &= ~EcsIsEntity; - term_id->flags |= EcsIsVariable; + 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; + } } - /* 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; - } + ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }"); + return NULL; +} - if (free_name) { - ecs_os_free(name); - } +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; - term_id->name = NULL; + 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; + } } - return 0; + ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); + return NULL; } -static -int flecs_term_ids_finalize( - const ecs_world_t *world, - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) +void flecs_json_next( + ecs_strbuf_t *buf) { - ecs_term_id_t *src = &term->src; - ecs_term_id_t *first = &term->first; - ecs_term_id_t *second = &term->second; + ecs_strbuf_list_next(buf); +} - /* Include inherited components (like from prefabs) by default for src */ - if (!(src->flags & EcsTraverseFlags)) { - src->flags |= EcsSelf | EcsUp; - } +void flecs_json_number( + ecs_strbuf_t *buf, + double value) +{ + ecs_strbuf_appendflt(buf, value, '"'); +} - /* 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; - } - } - } +void flecs_json_true( + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendlit(buf, "true"); +} - /* Traverse Self by default for pair target */ - if (!(second->flags & EcsTraverseFlags)) { - second->flags |= EcsSelf; - } +void flecs_json_false( + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendlit(buf, "false"); +} - /* Source defaults to This */ - if ((src->id == 0) && (src->name == NULL) && !(src->flags & EcsIsEntity)) { - src->id = EcsThis; - src->flags |= EcsIsVariable; +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value) +{ + if (value) { + flecs_json_true(buf); + } else { + flecs_json_false(buf); } +} - /* Initialize term identifier flags */ - if (flecs_term_id_finalize_flags(src, ctx)) { - return -1; - } - if (flecs_term_id_finalize_flags(first, ctx)) { - return -1; - } +void flecs_json_array_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "[", ", "); +} - if (flecs_term_id_finalize_flags(second, ctx)) { - return -1; - } +void flecs_json_array_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "]"); +} - /* 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; - } +void flecs_json_object_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "{", ", "); +} - ecs_entity_t first_id = 0; - ecs_entity_t oneof = 0; - if (first->flags & EcsIsEntity) { - first_id = first->id; +void flecs_json_object_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "}"); +} - /* 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); - } +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, value); + ecs_strbuf_appendch(buf, '"'); +} - if (flecs_term_id_lookup(world, oneof, &term->second, term->move, ctx)) { - return -1; +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_zerocpy(buf, out); } +} - /* 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; - } +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name) +{ + flecs_json_membern(buf, name, ecs_os_strlen(name)); +} - /* If source is wildcard, term won't return any data */ - if ((src->flags & EcsIsVariable) && ecs_id_is_wildcard(src->id)) { - term->inout |= EcsInOutNone; - } +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, "\":"); +} - return 0; +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 -ecs_entity_t flecs_term_id_get_entity( - const ecs_term_id_t *term_id) +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) { - 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; - } + const char *lbl = NULL; +#ifdef FLECS_DOC + lbl = ecs_doc_get_name(world, e); +#else + lbl = ecs_get_name(world, e); +#endif + + if (lbl) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, lbl); + ecs_strbuf_appendch(buf, '"'); } else { - return 0; /* Term id is uninitialized */ + ecs_strbuf_appendch(buf, '0'); } } -static -int flecs_term_populate_id( - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) { - 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; - } + (void)world; + (void)e; - if ((second || term->second.flags == EcsIsEntity) && !role) { - role = term->id_flags = ECS_PAIR; - } + const char *color = NULL; +#ifdef FLECS_DOC + color = ecs_doc_get_color(world, e); +#endif - if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { - term->id = first | role; + if (color) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, color); + ecs_strbuf_appendch(buf, '"'); } else { - if (!ECS_HAS_ID_FLAG(role, PAIR)) { - flecs_filter_error(ctx, "invalid role for pair"); - return -1; - } - - term->id = ecs_pair(first, second); + ecs_strbuf_appendch(buf, '0'); } - - return 0; } -static -int flecs_term_populate_from_id( +void flecs_json_id( + ecs_strbuf_t *buf, const ecs_world_t *world, - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) + ecs_id_t id) { - ecs_entity_t first = 0; - ecs_entity_t second = 0; - ecs_id_t role = term->id & ECS_ID_FLAGS_MASK; + ecs_strbuf_appendch(buf, '['); - if (!role && term->id_flags) { - role = term->id_flags; - term->id |= role; + 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, '"'); } - if (term->id_flags && term->id_flags != role) { - flecs_filter_error(ctx, "mismatch between term.id & term.id_flags"); - return -1; - } + ecs_strbuf_appendch(buf, ']'); +} - term->id_flags = role; +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind) +{ + return kind - EcsOpPrimitive; +} - if (ECS_HAS_ID_FLAG(term->id, PAIR)) { - first = ECS_PAIR_FIRST(term->id); - second = ECS_PAIR_SECOND(term->id); +#endif - 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; - } - } +/** + * @file json/serialize.c + * @brief Serialize (component) values to JSON strings. + */ - ecs_entity_t term_first = flecs_term_id_get_entity(&term->first); - if (term_first) { - if ((uint32_t)term_first != 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; - } - } +#ifdef FLECS_JSON - return 0; -} +/* Cached id records during serialization */ +typedef struct ecs_json_ser_idr_t { + ecs_id_record_t *idr_doc_name; + ecs_id_record_t *idr_doc_color; +} ecs_json_ser_idr_t; 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; - } +int json_ser_type( + const ecs_world_t *world, + const ecs_vec_t *ser, + const void *base, + ecs_strbuf_t *str); - if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { - flecs_filter_error(ctx, "both sides of operator cannot be a name"); - goto error; - } +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); - if ((src->flags & EcsIsEntity) && (second->flags & EcsIsEntity)) { - flecs_filter_error(ctx, "both sides of operator cannot be an entity"); - goto error; - } +static +int json_ser_type_op( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str); - if (!(src->flags & EcsIsVariable)) { - flecs_filter_error(ctx, "left-hand of operator must be a variable"); - goto error; - } +/* 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); - if (first_id == EcsPredMatch && !(second->flags & EcsIsName)) { - flecs_filter_error(ctx, "right-hand of match operator must be a string"); + 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; } - 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; - } - } + 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 flecs_term_verify( +int json_ser_bitmask( const ecs_world_t *world, - const ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) + ecs_meta_type_op_t *op, + const void *ptr, + ecs_strbuf_t *str) { - 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; - } + const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); + ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); - if (!ecs_term_id_is_set(first)) { - flecs_filter_error(ctx, "term.first is not initialized"); - return -1; + uint32_t value = *(const uint32_t*)ptr; + if (!value) { + ecs_strbuf_appendch(str, '0'); + return 0; } - 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; - } + ecs_strbuf_list_push(str, "\"", "|"); - 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; + /* 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 (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 (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; } - 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; - } - } + ecs_strbuf_list_pop(str, "\""); return 0; +error: + return -1; } +/* Serialize elements of a contiguous array */ static -int flecs_term_finalize( +int json_ser_elements( const ecs_world_t *world, - ecs_term_t *term, - ecs_filter_finalize_ctx_t *ctx) + 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) { - 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, ctx)) { - return -1; - } - } + flecs_json_array_push(str); - /* 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)) { - term->oper = EcsAnd; - term->id = ecs_pair(EcsChildOf, 0); - term->second.id = 0; - term->second.flags |= EcsIsEntity; - term->second.flags &= ~EcsIsVariable; - } + const void *ptr = base; - ecs_entity_t first_id = 0; - if (term->first.flags & EcsIsEntity) { - first_id = term->first.id; + 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); } - term->idr = flecs_query_id_record_get(world, term->id); - ecs_flags32_t id_flags = term->idr ? term->idr->flags : 0; + flecs_json_array_pop(str); - if (first_id) { - ecs_entity_t first_trav = first->trav; + return 0; +} - /* 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; - } +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); - 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; - } - } - } + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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; - } + 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); - 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; - } - } - } - } + return json_ser_elements( + world, ops, op_count, base, elem_count, comp->size, str, is_array); +} - /* 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; - } +/* 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); - /* 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; - } - } + return json_ser_type_elements( + world, a->type, ptr, a->count, str, true); +} - /* 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; - } - } - } - } +/* 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); - if (ecs_table_has_id(world, first_table, EcsReflexive)) { - term->flags |= EcsTermReflexive; - } - } - } + int32_t count = ecs_vec_count(value); + void *array = ecs_vec_first(value); - if (first->id == EcsVariable) { - flecs_filter_error(ctx, "invalid $ for term.first"); - return -1; - } + /* Serialize contiguous buffer of vector */ + return json_ser_type_elements(world, v->type, array, count, str, false); +} - if (term->id_flags & ECS_AND) { - term->oper = EcsAndFrom; - term->id &= ECS_COMPONENT_MASK; - term->id_flags = 0; - } +typedef struct json_serializer_ctx_t { + ecs_strbuf_t *str; + bool is_collection; + bool is_struct; +} json_serializer_ctx_t; - 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; - } +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); +} - if (flecs_term_verify(world, term, ctx)) { +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; } -ecs_id_t flecs_to_public_id( - ecs_id_t id) +static +int json_ser_custom_type( + const ecs_world_t *world, + ecs_meta_type_op_t *op, + const void *base, + ecs_strbuf_t *str) { - if (ECS_PAIR_FIRST(id) == EcsUnion) { - return ecs_pair(ECS_PAIR_SECOND(id), EcsWildcard); - } else { - return id; + 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; } -} -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); - } + 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; } - return id; -} + if (kind == EcsStructType) { + flecs_json_object_pop(str); + } else if (kind == EcsArrayType || kind == EcsVectorType) { + flecs_json_array_pop(str); + } -bool ecs_identifier_is_0( - const char *id) -{ - return id[0] == '0' && !id[1]; + return 0; } -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern) +/* 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) { - if (id == pattern) { - return true; + 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*)ECS_OFFSET(ptr, op->offset), '"'); + break; + case EcsOpF64: + ecs_strbuf_appendflt(str, + *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); + break; + case EcsOpEnum: + if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpBitmask: + if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpArray: + if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpVector: + if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpOpaque: + if (json_ser_custom_type(world, op, ECS_OFFSET(ptr, op->offset), str)) { + goto error; + } + break; + case EcsOpEntity: { + ecs_entity_t e = *(const ecs_entity_t*)ECS_OFFSET(ptr, op->offset); + if (!e) { + ecs_strbuf_appendch(str, '0'); + } else { + flecs_json_path(str, world, e); + } + break; } - - if (ECS_HAS_ID_FLAG(pattern, PAIR)) { - if (!ECS_HAS_ID_FLAG(id, PAIR)) { - return false; + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + 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); + } - 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); + return 0; +error: + return -1; +} - ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); +/* 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]; - 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; + if (in_array <= 0) { + if (op->name) { + flecs_json_member(str, op->name); } - } 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; + + 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; } - } 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; + + 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 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 false; + return -1; } -bool ecs_id_is_pair( - ecs_id_t id) +/* Iterate over the type ops of a type */ +static +int json_ser_type( + const ecs_world_t *world, + const ecs_vec_t *v_ops, + const void *base, + ecs_strbuf_t *str) { - return ECS_HAS_ID_FLAG(id, PAIR); + 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); } -bool ecs_id_is_wildcard( - ecs_id_t id) +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 ((id == EcsWildcard) || (id == EcsAny)) { - return true; - } - - bool is_pair = ECS_IS_PAIR(id); - if (!is_pair) { - return false; - } + if (count) { + ecs_size_t size = comp->size; - ecs_entity_t first = ECS_PAIR_FIRST(id); - ecs_entity_t second = ECS_PAIR_SECOND(id); + flecs_json_array_push(buf); - return (first == EcsWildcard) || (second == EcsWildcard) || - (first == EcsAny) || (second == EcsAny); -} + do { + ecs_strbuf_list_next(buf); + if (json_ser_type(world, &ser->ops, ptr, buf)) { + return -1; + } -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; - } + ptr = ECS_OFFSET(ptr, size); + } while (-- count); - 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; + flecs_json_array_pop(buf); + } else { + if (json_ser_type(world, &ser->ops, ptr, buf)) { + return -1; } } - return true; + return 0; } -ecs_flags32_t ecs_id_get_flags( +int ecs_array_to_json_buf( const ecs_world_t *world, - ecs_id_t id) + ecs_entity_t type, + const void *ptr, + int32_t count, + ecs_strbuf_t *buf) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - return idr->flags; - } else { - return 0; + 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; } -} -bool ecs_term_id_is_set( - const ecs_term_id_t *id) -{ - return id->id != 0 || id->name != NULL || id->flags & EcsIsEntity; + return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } -bool ecs_term_is_initialized( - const ecs_term_t *term) +char* ecs_array_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr, + int32_t count) { - return term->id != 0 || ecs_term_id_is_set(&term->first); -} + ecs_strbuf_t str = ECS_STRBUF_INIT; -bool ecs_term_match_this( - const ecs_term_t *term) -{ - return (term->src.flags & EcsIsVariable) && (term->src.id == EcsThis); -} + if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } -bool ecs_term_match_0( - const ecs_term_t *term) -{ - return (term->src.id == 0) && (term->src.flags & EcsIsEntity); + return ecs_strbuf_get(&str); } -int ecs_term_finalize( +int ecs_ptr_to_json_buf( 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) + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf) { - 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; - } + return ecs_array_to_json_buf(world, type, ptr, 0, buf); } -void ecs_term_fini( - ecs_term_t *term) +char* ecs_ptr_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) { - ecs_os_free(term->first.name); - ecs_os_free(term->src.name); - ecs_os_free(term->second.name); - ecs_os_free(term->name); - - term->first.name = NULL; - term->src.name = NULL; - term->second.name = NULL; - term->name = NULL; + return ecs_array_to_json(world, type, ptr, 0); } static -ecs_term_t* flecs_filter_or_other_type( - ecs_filter_t *f, - int32_t t) +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) { - ecs_term_t *term = &f->terms[t]; - ecs_term_t *first = NULL; - while (t--) { - if (f->terms[t].oper != EcsOr) { - break; + 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; } - 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); + if (is_base) { + if (ecs_has_id(world, pred, EcsDontInherit)) { + return true; } - 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 (!desc || !desc->serialize_private) { + if (ecs_has_id(world, pred, EcsPrivate)) { + return true; } - - if (first_type == term_type) { - return NULL; + } + if (is_base) { + if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { + hidden = true; } - return first; - } else { - return NULL; } + 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; } -int ecs_filter_finalize( - const ecs_world_t *world, - ecs_filter_t *f) +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) { - 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; + (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; + } - ecs_filter_finalize_ctx_t ctx = {0}; - ctx.world = world; - ctx.filter = f; + flecs_json_memberl(buf, "id_labels"); + flecs_json_array_push(buf); - f->flags |= EcsFilterMatchOnlyThis; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ctx.term_index = i; - if (flecs_term_finalize(world, term, &ctx)) { - return -1; + 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 (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; + 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; } - } else { - field_count ++; } - if (term->oper == EcsOr || (i && term[-1].oper == EcsOr)) { - ecs_term_t *first = flecs_filter_or_other_type(f, i); - if (first) { - filter_terms ++; - if (first == &term[-1]) { - filter_terms ++; - } - } - } + if (desc && desc->serialize_id_labels) { + flecs_json_next(buf); - term->field_index = field_count - 1; + 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); + } - if (ecs_term_match_this(term)) { - ECS_BIT_SET(f->flags, EcsFilterMatchThis); - } else { - ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); + flecs_json_array_pop(buf); } + } - if (term->id == EcsPrefab) { - ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); - } - if (term->id == EcsDisabled && (term->src.flags & EcsSelf)) { - ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); - } + flecs_json_array_pop(buf); +#endif + return 0; +} - if (ECS_BIT_IS_SET(f->flags, EcsFilterNoData)) { - term->inout = EcsInOutNone; - } - - if (term->oper == EcsNot && term->inout == EcsInOutDefault) { - term->inout = EcsInOutNone; - } +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; + } - if (term->inout == EcsInOutNone) { - filter_terms ++; - } else if (term->idr) { - if (!term->idr->type_info && !(term->idr->flags & EcsIdUnion)) { - filter_terms ++; - } - } else if (ecs_id_is_tag(world, term->id)) { - 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 */ - filter_terms ++; - } - } - if ((term->id == EcsWildcard) || (term->id == - ecs_pair(EcsWildcard, EcsWildcard))) + 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)) { - /* 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; - } + continue; } - if (term->oper != EcsNot || !ecs_term_match_this(term)) { - ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); - } + 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); - if (term->idr) { - if (ecs_os_has_threading()) { - ecs_os_ainc(&term->idr->keep_alive); - } else { - term->idr->keep_alive ++; + flecs_json_next(buf); + if (json_ser_type(world, &ser->ops, ptr, buf) != 0) { + /* Entity contains invalid value */ + return -1; + } + serialized = true; + } } - } - - 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; + 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); } - - 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; - } + flecs_json_array_pop(buf); + + return 0; +} - if (term_count && (terms[term_count - 1].oper == EcsOr)) { - flecs_filter_error(&ctx, "last term of filter can't have OR operator"); - return -1; +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; } - f->field_count = 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; + flecs_json_memberl(buf, "type_info"); + flecs_json_array_push(buf); - if (term->oper == EcsOr || (i && (term[-1].oper == EcsOr))) { - if (flecs_filter_or_other_type(f, i)) { - f->sizes[field] = 0; - continue; - } - } + 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 (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); - } else if (idr->type_info) { - f->sizes[field] = idr->type_info->size; - } + 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 { - 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); - } else { - const ecs_type_info_t *ti = ecs_get_type_info( - world, term->id); - if (ti) { - f->sizes[field] = ti->size; - } - } + flecs_json_next(buf); + flecs_json_number(buf, 0); + } + } else { + if (!desc || desc->serialize_hidden) { + flecs_json_next(buf); + flecs_json_number(buf, 0); } } - } else { - f->sizes = NULL; - } - - if (filter_terms >= term_count) { - ECS_BIT_SET(f->flags, EcsFilterNoData); } - ECS_BIT_COND(f->flags, EcsFilterHasCondSet, cond_set); - + flecs_json_array_pop(buf); + 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) +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) { - ecs_poly_assert(poly, ecs_filter_t); + if (!desc || !desc->serialize_hidden) { + return 0; + } - if (filter) { - iter[1] = ecs_filter_iter(world, (ecs_filter_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); - } else { - iter[0] = ecs_filter_iter(world, (ecs_filter_t*)poly); + if (ent == inst) { + return 0; /* if this is not a base, components are never hidden */ } -} -/* 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]); - } + flecs_json_memberl(buf, "hidden"); + flecs_json_array_push(buf); - if (filter->terms_owned) { - /* Memory allocated for both terms & sizes */ - ecs_os_free(filter->terms); - } else { - ecs_os_free(filter->sizes); + 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; } - } - - filter->terms = NULL; - if (filter->owned) { - ecs_os_free(filter); + flecs_json_next(buf); + flecs_json_bool(buf, hidden); } -} -void ecs_filter_fini( - ecs_filter_t *filter) -{ - if (filter->owned && filter->entity) { - /* If filter is associated with entity, use poly dtor path */ - ecs_delete(filter->world, filter->entity); - } else { - flecs_filter_fini(filter); - } + flecs_json_array_pop(buf); + + return 0; } -ecs_filter_t* ecs_filter_init( - ecs_world_t *world, - const ecs_filter_desc_t *desc) +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) { - 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->owned = true; - } - if (!storage_terms) { - f->terms_owned = true; - } - - ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); - ECS_BIT_SET(f->flags, EcsFilterMatchAnything); - f->flags |= desc->flags; - f->world = world; + const ecs_id_t *ids = NULL; + int32_t i, count = 0; - /* 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); + const ecs_type_t *type = ecs_get_type(world, ent); + if (type) { + ids = type->array; + count = type->count; } - /* 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}; - int32_t expr_size = 0; - - if (entity) { - name = ecs_get_name(world, entity); - } + if (!desc || desc->serialize_ids) { + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; + 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 (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); + 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; + } } - expr_terms[expr_count ++] = term; - if (ptr[0] == '\n') { - break; + 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); } - - if (!ptr) { - /* Set terms in filter object to make sur they get cleaned up */ - f->terms = expr_terms; - f->term_count = expr_count; - f->terms_owned = true; - goto error; - } -#else - (void)expr; - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif + flecs_json_array_pop(buf); } - /* 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->terms_owned == true, ECS_INTERNAL_ERROR, NULL); - f->term_count = term_count + expr_count; - ecs_size_t terms_size = ECS_SIZEOF(ecs_term_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); - f->sizes = ECS_OFFSET(f->terms, terms_size); - } else { - f->terms = storage_terms; - f->term_count = storage_count; - f->sizes = ecs_os_calloc_n(ecs_size_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); + if (flecs_json_append_type_labels(world, buf, ids, count, ent, inst, desc)) { + return -1; } - - /* Ensure all fields are consistent and properly filled out */ - if (ecs_filter_finalize(world, f)) { - goto error; + + if (flecs_json_append_type_values(world, buf, ids, count, ent, inst, desc)) { + return -1; } - /* Any allocated resources remaining in terms are now owned by filter */ - for (i = 0; i < f->term_count; i ++) { - f->terms[i].move = false; + if (flecs_json_append_type_info(world, buf, ids, count, ent, inst, desc)) { + return -1; } - 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->owned) { - EcsPoly *poly = ecs_poly_bind(world, entity, ecs_filter_t); - poly->poly = f; - ecs_poly_modified(world, entity, ecs_filter_t); + if (flecs_json_append_type_hidden(world, buf, ids, count, ent, inst, desc)) { + return -1; } - return f; -error: - ecs_filter_fini(f); - return NULL; + return 0; } -void ecs_filter_copy( - ecs_filter_t *dst, - const ecs_filter_t *src) +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) { - if (src == dst) { - return; + 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; } - 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; - dst->terms = ecs_os_malloc(terms_size + sizes_size); - dst->sizes = ECS_OFFSET(dst->terms, terms_size); - dst->terms_owned = true; - ecs_os_memcpy_n(dst->sizes, src->sizes, int32_t, term_count); - - for (i = 0; i < term_count; i ++) { - dst->terms[i] = ecs_term_copy(&src->terms[i]); + 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; + } } - } 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; - } + ecs_strbuf_list_next(buf); + flecs_json_object_push(buf); + flecs_json_memberl(buf, "path"); + flecs_json_path(buf, world, ent); - if (src) { - *dst = *src; - if (src->terms_owned) { - dst->terms = src->terms; - dst->sizes = src->sizes; - dst->terms_owned = true; - } else { - ecs_filter_copy(dst, src); - } - src->terms = NULL; - src->sizes = NULL; - src->term_count = 0; - } else { - ecs_os_memset_t(dst, 0, ecs_filter_t); + if (flecs_json_append_type(world, buf, ent, inst, desc)) { + return -1; } + + flecs_json_object_pop(buf); + + return 0; } +#ifdef FLECS_ALERTS static -void flecs_filter_str_add_id( +int flecs_json_serialize_entity_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, - const ecs_term_id_t *id, - bool is_subject, - ecs_flags32_t default_traverse_flags) + ecs_entity_t entity, + const EcsAlertsActive *alerts, + bool self) { - 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"); + 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); + } } - is_added = true; + flecs_json_object_pop(buf); } - 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; - } + return 0; +} - 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"); - } +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) }} + }); - if (id->trav && (id->trav != EcsIsA)) { - ecs_strbuf_list_push(buf, "(", ""); + ecs_iter_t it = ecs_filter_iter(world, &f); + while (ecs_filter_next(&it)) { + ecs_record_t **records = ecs_vec_first(&it.table->data.records); + EcsAlertsActive *alerts = ecs_table_get_id( + world, it.table, ecs_id(EcsAlertsActive), it.offset); - char *rel_path = ecs_get_fullpath(world, id->trav); - ecs_strbuf_appendstr(buf, rel_path); - ecs_os_free(rel_path); + 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_strbuf_list_pop(buf, ")"); + ecs_record_t *r = records[i]; + if (r->row & EcsEntityIsTraversable) { + if (flecs_json_serialize_children_alerts( + world, buf, child)) + { + goto error; + } + } } - - ecs_strbuf_list_pop(buf, ""); } + + ecs_filter_fini(&f); + + return 0; +error: + return -1; } +#endif static -void flecs_term_str_w_strbuf( +int flecs_json_serialize_alerts( const ecs_world_t *world, - const ecs_term_t *term, ecs_strbuf_t *buf, - int32_t t) + ecs_entity_t entity) { - 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); + (void)world; + (void)buf; + (void)entity; - if (term->first.id == EcsScopeOpen) { - ecs_strbuf_appendlit(buf, "{"); - return; - } else if (term->first.id == EcsScopeClose) { - ecs_strbuf_appendlit(buf, "}"); - return; +#ifdef FLECS_ALERTS + if (!ecs_id(EcsAlertsActive)) { + return 0; /* Alert module not imported */ } - 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] "); - } + 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; +} - if (term->first.flags & EcsIsEntity && term->first.id != 0) { - if (ecs_has_id(world, term->first.id, EcsDontInherit)) { - def_src_mask = EcsSelf; - } - } +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)); - if (term->oper == EcsNot) { - ecs_strbuf_appendlit(buf, "!"); - } else if (term->oper == EcsOptional) { - ecs_strbuf_appendlit(buf, "?"); - } + flecs_json_member(buf, id_str); + ecs_os_free(id_str); - 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_json_array_push(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_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); + } } - 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); + flecs_json_array_pop(buf); + + return 0; } static -char* flecs_filter_str( +int flecs_json_serialize_refs( const ecs_world_t *world, - const ecs_filter_t *filter, - const ecs_filter_finalize_ctx_t *ctx, - int32_t *term_start_out) + ecs_strbuf_t *buf, + ecs_entity_t entity, + ecs_entity_t relationship) { - 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]; + ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_pair(relationship, entity)); - 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 + , */ - } + 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; +} - flecs_term_str_w_strbuf(world, term, &buf, i); +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 (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, ", "); + 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 ecs_strbuf_get(&buf); -error: - return NULL; + + return 0; } -char* ecs_filter_str( +int ecs_entity_to_json_buf( 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_entity_t entity, + ecs_strbuf_t *buf, + const ecs_entity_to_json_desc_t *desc) { - 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; + if (!entity || !ecs_is_valid(world, entity)) { + return -1; } -error: - return -1; -} + flecs_json_object_push(buf); -/* 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 (!desc || desc->serialize_path) { + flecs_json_memberl(buf, "path"); + flecs_json_path(buf, world, entity); } - if (ECS_PAIR_FIRST(id) == EcsAny) { - return true; - } - if (ECS_PAIR_SECOND(id) == EcsAny) { - return true; +#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); + } } - 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 (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 (!result && oper == EcsAndFrom) { - return false; - } else - if (result && oper == EcsOrFrom) { - return true; + } + + 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 (oper == EcsAndFrom) { - if (id_out) { - id_out[0] = type_id; + 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); } - return true; - } else - if (oper == EcsOrFrom) { - return false; } +#endif - return false; -} + 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; + } -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; + if (!desc || desc->serialize_base) { + if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { + flecs_json_memberl(buf, "is_a"); + flecs_json_array_push(buf); - 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 */ + 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); } - 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 (flecs_json_append_type(world, buf, entity, entity, desc)) { + goto error; } - /* 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 (desc && desc->serialize_alerts) { + if (flecs_json_serialize_alerts(world, buf, entity)) { + goto error; } + } - match_table = ecs_get_table(world, src_id); - if (match_table) { - } else if (oper != EcsOptional) { - return false; + 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; } - } else { - /* If filter contains This terms, a table must be provided */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_json_object_pop(buf); } - if (!match_table) { - return false; + 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); } - ecs_entity_t source = 0; + flecs_json_object_pop(buf); - /* 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; - } + return 0; +error: + return -1; +} - /* Remove base 1 offset */ - column --; - } +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; - /* Find location, source and id of match in table type */ - ecs_table_record_t *tr = 0; - bool is_any = is_any_pair(id); + if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { + ecs_strbuf_reset(&buf); + return NULL; + } - column = flecs_search_relation_w_idr(world, match_table, - column, id, src->trav, src->flags, &source, id_out, &tr, term->idr); + return ecs_strbuf_get(&buf); +} - if (tr && match_index_out) { - if (!is_any) { - match_index_out[0] = tr->count; - } else { - match_index_out[0] = 1; - } +static +bool flecs_json_skip_variable( + const char *name) +{ + if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { + return true; + } else { + return false; } +} - bool result = column != -1; +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); +} - if (oper == EcsNot) { - if (match_index_out) { - match_index_out[0] = 1; - } - result = !result; +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); } - if (oper == EcsOptional) { - result = true; + 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); +} - 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; - } +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; } - if (!result) { - if (iter_flags & EcsFilterPopulate) { - column = 0; - } else { - return false; - } - } + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); - if (!ecs_term_match_this(term)) { - if (!source) { - source = src_id; - } + for (int i = 0; i < field_count; i ++) { + flecs_json_next(buf); + flecs_json_serialize_id(world, it->terms[i].id, buf); } - if (id_out && column < 0) { - id_out[0] = id; - } + flecs_json_array_pop(buf); +} - if (column_out) { - if (column >= 0) { - column ++; - if (source != 0) { - column *= -1; - } - column_out[0] = column; - } else { - column_out[0] = 0; - } +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; } - if (subject_out) { - subject_out[0] = source; + 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); } - return result; + flecs_json_array_pop(buf); } -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) +static +void flecs_json_serialize_id_str( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) { - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; - int32_t match_count = 1; - bool result = true; + 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, '"'); +} - if (matches_left) { - match_count = *matches_left; +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; } - 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; - } - } + if (it->flags & EcsIterNoData) { + return; + } - ecs_term_id_t *src = &term->src; - const ecs_table_t *match_table = table; - int32_t t_i = term->field_index; + flecs_json_memberl(buf, "type_info"); + flecs_json_object_push(buf); - ecs_entity_t src_id = src->id; - if (!src_id) { - if (ids) { - ids[t_i] = term->id; - } - continue; + 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 (!ecs_term_match_this(term)) { - match_table = ecs_get_table(world, src_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 { - 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); + flecs_json_serialize_id_str(world, it->terms[i].id, buf); + ecs_strbuf_appendlit(buf, ":0"); } + } - int32_t match_index = 0; - if (!i || term[-1].oper != EcsOr) { - result = false; - } else { - if (result) { - continue; /* Already found matching OR term */ - } - } + flecs_json_object_pop(buf); +} - 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); +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; - if (i && term[-1].oper == EcsOr) { - result |= term_result; - } else { - result = term_result; - } + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; - if (oper != EcsOr && !result) { - return false; + if (!actual_count) { + flecs_json_memberl(buf, "vars"); + flecs_json_array_push(buf); + actual_count ++; } - if (first && match_index) { - match_count *= match_index; - } - if (match_indices) { - match_indices[t_i] = match_index; - } + ecs_strbuf_list_next(buf); + flecs_json_string(buf, var_name); } - if (matches_left) { - *matches_left = match_count; + if (actual_count) { + flecs_json_array_pop(buf); } - - 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 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 term_iter_init_wildcard( +void flecs_json_serialize_iter_result_id_labels( const ecs_world_t *world, - ecs_term_iter_t *iter, - bool empty_tables) + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - 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); + 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 term_iter_init( +void flecs_json_serialize_iter_result_table_type( 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; + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + if (!it->table) { + return; + } - iter->term = *term; + if (desc->serialize_ids) { + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); - if (src->flags & EcsSelf) { - iter->self_index = term->idr; - if (!iter->self_index) { - iter->self_index = flecs_query_id_record_get(world, term->id); + 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); } - } - if (src->flags & EcsUp) { - iter->set_index = flecs_id_record_get(world, - ecs_pair(src->trav, EcsWildcard)); + flecs_json_array_pop(buf); } + if (desc->serialize_id_labels) { + flecs_json_memberl(buf, "id_labels"); + flecs_json_array_push(buf); - ecs_id_record_t *idr; - if (iter->self_index) { - idr = iter->cur = iter->self_index; - } else { - idr = iter->cur = iter->set_index; - } + 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); + } - term_iter_init_w_idr(term, iter, idr, empty_tables); + flecs_json_array_pop(buf); + } } -ecs_iter_t ecs_term_iter( - const ecs_world_t *stage, - ecs_term_t *term) +static +void flecs_json_serialize_iter_result_sources( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_json_memberl(buf, "sources"); + flecs_json_array_push(buf); - const ecs_world_t *world = ecs_get_world(stage); + 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_process_pending_tables(world); + flecs_json_array_pop(buf); +} - if (ecs_term_finalize(world, term)) { - ecs_throw(ECS_INVALID_PARAMETER, NULL); +static +void flecs_json_serialize_iter_result_is_set( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + if (!(it->flags & EcsIterHasCondSet)) { + return; } - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .field_count = 1, - .next = ecs_term_next - }; + flecs_json_memberl(buf, "is_set"); + flecs_json_array_push(buf); - /* 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 accomodate 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); + 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); + } + } - return it; -error: - return (ecs_iter_t){ 0 }; + flecs_json_array_pop(buf); } -ecs_iter_t ecs_term_chain_iter( - const ecs_iter_t *chain_it, - ecs_term_t *term) +static +void flecs_json_serialize_iter_result_variables( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - 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); - } + char **variable_names = it->variable_names; + ecs_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = chain_it->world, - .terms = term, - .field_count = 1, - .chain_it = (ecs_iter_t*)chain_it, - .next = ecs_term_next - }; + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; - flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); + if (!actual_count) { + flecs_json_memberl(buf, "vars"); + flecs_json_array_push(buf); + actual_count ++; + } - term_iter_init(world, term, &it.priv.iter.term, false); + ecs_strbuf_list_next(buf); + flecs_json_path(buf, world, variables[i].entity); + } - return it; -error: - return (ecs_iter_t){ 0 }; + if (actual_count) { + flecs_json_array_pop(buf); + } } -ecs_iter_t ecs_children( +static +void flecs_json_serialize_iter_result_variable_labels( const ecs_world_t *world, - ecs_entity_t parent) + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - return ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(parent) }); -} + char **variable_names = it->variable_names; + ecs_var_t *variables = it->variables; + int32_t var_count = it->variable_count; + int32_t actual_count = 0; -bool ecs_children_next( - ecs_iter_t *it) -{ - return ecs_term_next(it); + 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 -const ecs_table_record_t *flecs_term_iter_next_table( - ecs_term_iter_t *iter) +void flecs_json_serialize_iter_result_variable_ids( + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ecs_id_record_t *idr = iter->cur; - if (!idr) { - return NULL; + 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); } - return flecs_table_cache_next(&iter->it, ecs_table_record_t); + if (actual_count) { + flecs_json_array_pop(buf); + } } 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) +bool flecs_json_serialize_iter_result_entity_names( + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ecs_term_id_t *src = &term->src; - - /* Test if following the relationship finds the id */ - int32_t index = flecs_search_relation_w_idr(world, table, 0, - term->id, src->trav, src->flags, source, id, 0, term->idr); + ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); - if (index == -1) { - *source = 0; + EcsIdentifier *names = ecs_table_get_id(it->world, it->table, + ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); + if (!names) { return false; } - ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); - - *column = (index + 1) * -1; + int i; + for (i = 0; i < it->count; i ++) { + flecs_json_next(buf); + flecs_json_string(buf, names[i].value); + } return true; } static -bool flecs_term_iter_next( - ecs_world_t *world, - ecs_term_iter_t *iter, - bool match_prefab, - bool match_disabled) +void flecs_json_serialize_iter_result_entity_ids( + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - 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 (!it->count) { + return; + } - 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); - } - } + flecs_json_memberl(buf, "entity_ids"); + flecs_json_array_push(buf); - if (!tr) { - return false; - } - } + ecs_entity_t *entities = it->entities; - table = tr->hdr.table; - if (table->_->traversable_count) { - iter->observed_table_count ++; - } + int i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_number(buf, (double)(uint32_t)entities[i]); + } - if (!match_prefab && (table->flags & EcsTableIsPrefab)) { - continue; - } + flecs_json_array_pop(buf); +} - if (!match_disabled && (table->flags & EcsTableIsDisabled)) { - continue; - } +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; + } - iter->table = table; - iter->match_count = tr->count; - if (is_any_pair(term->id)) { - iter->match_count = 1; - } + ecs_table_record_t *tr = flecs_id_record_get_table( + world->idr_childof_wildcard, it->table); + if (tr == NULL) { + return; + } - iter->cur_match = 0; - iter->last_column = tr->column; - iter->column = tr->column + 1; - iter->id = flecs_to_public_id(table->type.array[tr->column]); - } + 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); +} - 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; - } - } +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; + } - if (!flecs_term_iter_find_superset( - world, table, term, &source, &iter->id, &iter->column)) - { - continue; - } + flecs_json_serialize_iter_result_parent(world, it, buf); - /* 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; - } + flecs_json_memberl(buf, "entities"); + flecs_json_array_push(buf); - break; - } while (true); + if (!flecs_json_serialize_iter_result_entity_names(it, buf)) { + ecs_entity_t *entities = it->entities; - iter->subject = source; + int i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_json_next(buf); + flecs_json_number(buf, (double)(uint32_t)entities[i]); + } + } - return true; + flecs_json_array_pop(buf); } static -bool flecs_term_iter_set_table( - ecs_world_t *world, - ecs_term_iter_t *iter, - ecs_table_t *table) +void flecs_json_serialize_iter_result_entity_labels( + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_json_ser_idr_t *ser_idr) { - 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->column; - iter->column = tr->column + 1; - iter->id = flecs_to_public_id(table->type.array[tr->column]); - } + (void)buf; + (void)ser_idr; + if (!it->count) { + return; } - 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 (!ser_idr->idr_doc_name) { + return; } - if (!tr) { - return false; +#ifdef FLECS_DOC + ecs_table_t *table = it->table; + ecs_table_record_t *tr = flecs_id_record_get_table( + ser_idr->idr_doc_name, table); + if (tr == NULL) { + return; } - /* Populate fields as usual */ - iter->table = table; - iter->cur_match = 0; - - return true; -} + EcsDocDescription *labels = ecs_table_get_column( + table, tr->column, it->offset); + ecs_assert(labels != NULL, ECS_INTERNAL_ERROR, NULL); -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_json_memberl(buf, "entity_labels"); + flecs_json_array_push(buf); - flecs_iter_validate(it); + int i; + for (i = 0; i < it->count; i ++) { + flecs_json_next(buf); + flecs_json_string(buf, labels[i].value); + } - 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; + flecs_json_array_pop(buf); +#endif +} - it->ids = &iter->id; - it->sources = &iter->subject; - it->columns = &iter->column; - it->terms = &iter->term; - it->sizes = &iter->size; - it->ptrs = &iter->ptr; +static +void flecs_json_serialize_iter_result_colors( + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_json_ser_idr_t *ser_idr) +{ + (void)buf; + (void)ser_idr; - ecs_iter_t *chain_it = it->chain_it; - if (chain_it) { - ecs_iter_next_action_t next = chain_it->next; - bool match; + if (!it->count) { + return; + } - do { - if (!next(chain_it)) { - goto done; - } +#ifdef FLECS_DOC + if (!ser_idr->idr_doc_color) { + return; + } - 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; + ecs_table_record_t *tr = flecs_id_record_get_table( + ser_idr->idr_doc_color, it->table); + if (tr == NULL) { + return; + } - } else { - if (!flecs_term_iter_next(world, iter, false, false)) { - goto done; - } + EcsDocDescription *colors = ecs_table_get_column( + it->table, tr->column, it->offset); + ecs_assert(colors != NULL, ECS_INTERNAL_ERROR, NULL); - table = iter->table; + flecs_json_memberl(buf, "colors"); + flecs_json_array_push(buf); - /* 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); + int i; + for (i = 0; i < it->count; i ++) { + flecs_json_next(buf); + flecs_json_string(buf, colors[i].value); } -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; + flecs_json_array_pop(buf); +#endif } 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( +int flecs_json_serialize_iter_result_values( const ecs_world_t *world, - const ecs_filter_t *filter) + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + if (!it->ptrs || (it->flags & EcsIterNoData)) { + return 0; + } - 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; + 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_term_t *term = &terms[i]; - ecs_id_t id = term->id; + ecs_strbuf_list_next(buf); - if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { + 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; } - if (!ecs_term_match_this(term)) { + /* 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; } - 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 */ + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + /* Also odd, typeid but not a component? */ + ecs_strbuf_appendch(buf, '0'); + continue; } - 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; - } + 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 (self_pivot_term != -1) { - pivot_term = self_pivot_term; + /* 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)) { + ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL); + 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; + } + } } - return pivot_term; -error: - return -2; -} + flecs_json_array_pop(buf); -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)); + return 0; } -ecs_iter_t flecs_filter_iter_w_flags( - const ecs_world_t *stage, - const ecs_filter_t *filter, - ecs_flags32_t flags) +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_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_table_t *table = it->table; + if (!table || !table->column_count) { + return 0; } - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .terms = filter ? filter->terms : NULL, - .next = ecs_filter_next, - .flags = flags, - .sizes = filter->sizes - }; + flecs_json_memberl(buf, "values"); + flecs_json_array_push(buf); - ecs_filter_iter_t *iter = &it.priv.iter.filter; - iter->pivot_term = -1; + ecs_type_t *type = &table->type; + int32_t *column_map = table->column_map; + ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_init_filter_iter(&it, filter); - flecs_filter_apply_iter_flags(&it, filter); + for (int i = 0; i < type->count; i ++) { + int32_t storage_column = -1; + if (column_map) { + storage_column = column_map[i]; + } - /* 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); + 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; + } + } - pivot_term = ecs_filter_pivot_term(world, filter); - iter->kind = EcsIterEvalTables; - iter->pivot_term = pivot_term; + ecs_strbuf_list_next(buf); - 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)); + if (storage_column == -1) { + ecs_strbuf_appendch(buf, '0'); + continue; } - } else { - if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchAnything)) { - term_iter_init_no_data(&iter->term_iter); - } else { - iter->kind = EcsIterEvalNone; + + ecs_entity_t typeid = table->data.columns[storage_column].ti->component; + if (!typeid) { + ecs_strbuf_appendch(buf, '0'); + continue; } - } - ECS_BIT_COND(it.flags, EcsIterNoData, - ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); + const EcsComponent *comp = ecs_get(world, typeid, EcsComponent); + if (!comp) { + ecs_strbuf_appendch(buf, '0'); + continue; + } - 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; + const EcsMetaTypeSerialized *ser = ecs_get( + world, typeid, EcsMetaTypeSerialized); + if (!ser) { + ecs_strbuf_appendch(buf, '0'); + continue; + } - /* Set variable name array */ - it.variable_names = (char**)filter->variable_names; + 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_iter_init(stage, &it, flecs_iter_cache_all); - - return it; -error: - return (ecs_iter_t){ 0 }; -} + flecs_json_array_pop(buf); -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); + return 0; } -ecs_iter_t ecs_filter_chain_iter( - const ecs_iter_t *chain_it, - const ecs_filter_t *filter) +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, + const ecs_json_ser_idr_t *ser_idr) { - ecs_iter_t it = { - .terms = filter->terms, - .field_count = filter->field_count, - .world = chain_it->world, - .real_world = chain_it->real_world, - .chain_it = (ecs_iter_t*)chain_it, - .next = ecs_filter_next, - .sizes = filter->sizes - }; + flecs_json_next(buf); + flecs_json_object_push(buf); - 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); + /* 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); + } + } - iter->kind = EcsIterEvalChain; + /* 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); + } - return it; -} + /* Write variable values for current result */ + if (!desc || desc->serialize_variables) { + flecs_json_serialize_iter_result_variables(world, it, buf); + } -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); + /* Write labels for variables */ + if (desc && desc->serialize_variable_labels) { + flecs_json_serialize_iter_result_variable_labels(world, it, buf); + } - if (flecs_iter_next_row(it)) { - return true; + /* Write ids for variables */ + if (desc && desc->serialize_variable_ids) { + flecs_json_serialize_iter_result_variable_ids(it, buf); } - return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); -error: - return false; -} + /* 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); + } -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); + /* 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); + } - 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; + /* Write ids for entities */ + if (desc && desc->serialize_entity_ids) { + flecs_json_serialize_iter_result_entity_ids(it, buf); + } - flecs_iter_validate(it); + /* Write labels for entities */ + if (desc && desc->serialize_entity_labels) { + flecs_json_serialize_iter_result_entity_labels(it, buf, ser_idr); + } - ecs_iter_t *chain_it = it->chain_it; - ecs_iter_kind_t kind = iter->kind; + /* Write colors for entities */ + if (desc && desc->serialize_colors) { + flecs_json_serialize_iter_result_colors(it, buf, ser_idr); + } - 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)) { - ecs_iter_fini(it); - goto done; + /* 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; } + } + } - 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; + /* 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 - /* 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; + flecs_json_object_pop(buf); - /* 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); + return 0; +} - /* Can't set variable for filter that does not iterate tables */ - ecs_assert(kind == EcsIterEvalTables, - ECS_INVALID_OPERATION, NULL); - } - } +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); + } - 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; + flecs_json_object_push(buf); - 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; - } + /* Serialize component ids of the terms (usually provided by query) */ + if (!desc || desc->serialize_term_ids) { + flecs_json_serialize_iter_ids(world, it, buf); + } - /* 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; - } + if (desc && desc->serialize_term_labels) { + flecs_json_serialize_iter_id_labels(world, it, buf); + } - it->offset = this_var->range.offset; - it->count = this_var->range.count; + /* Serialize type info if enabled */ + if (desc && desc->serialize_type_info) { + flecs_json_serialize_type_info(world, it, buf); + } - /* But if it does, forward it to filter matching */ - ecs_assert(term_iter->table == this_table, - ECS_INTERNAL_ERROR, NULL); + /* Serialize variable names, if iterator has any */ + flecs_json_serialize_iter_variables(it, buf); - /* If This variable is not constrained, iterate as usual */ - } else { - it->offset = 0; - it->count = 0; + /* Serialize results */ + flecs_json_memberl(buf, "results"); + flecs_json_array_push(buf); - /* 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; - } - } + /* Use instancing for improved performance */ + ECS_BIT_SET(it->flags, EcsIterIsInstanced); - ecs_assert(term_iter->match_count != 0, - ECS_INTERNAL_ERROR, NULL); + /* 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); + } - 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; - } + /* Cache id record for flecs.doc ids */ + ecs_json_ser_idr_t ser_idr = {NULL, NULL}; +#ifdef FLECS_DOC + ser_idr.idr_doc_name = flecs_id_record_get(world, + ecs_pair_t(EcsDocDescription, EcsName)); + ser_idr.idr_doc_color = flecs_id_record_get(world, + ecs_pair_t(EcsDocDescription, EcsDocColor)); +#endif - iter->matches_left = term_iter->match_count; + ecs_iter_next_action_t next = it->next; + while (next(it)) { + if (flecs_json_serialize_iter_result(world, it, buf, desc, &ser_idr)) { + ecs_strbuf_reset(buf); + ecs_iter_fini(it); + return -1; + } + } - /* Filter iterator takes control over iterating all the - * permutations that match the wildcard. */ - term_iter->match_count = 1; + flecs_json_array_pop(buf); - table = term_iter->table; + if (desc && desc->measure_eval_duration) { + double dt = ecs_time_measure(&duration); + flecs_json_memberl(buf, "eval_duration"); + flecs_json_number(buf, dt); + } - 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; - } - } + flecs_json_object_pop(buf); - /* 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; - } + return 0; +} - /* 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; - } +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; - ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); - } + if (ecs_iter_to_json_buf(world, it, &buf, desc)) { + ecs_strbuf_reset(&buf); + return 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; + return ecs_strbuf_get(&buf); +} - /* 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; - } - } +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 matches_left > 0 we should've found at least one match */ - ecs_assert(i >= 0, ECS_INTERNAL_ERROR, NULL); + 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; - /* 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; - } + 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; + } + } - 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); + if (ecs_filter_init(world, &filter_desc) == NULL) { + return -1; + } - /* 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); - } - } + 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 + }; - match = iter->matches_left != 0; - iter->matches_left --; + int ret = ecs_iter_to_json_buf(world, &it, buf_out, &json_desc); + ecs_filter_fini(&f); + return ret; +} - ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); - } while (!match); +char* ecs_world_to_json( + ecs_world_t *world, + const ecs_world_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; - goto yield; + if (ecs_world_to_json_buf(world, &buf, desc)) { + ecs_strbuf_reset(&buf); + return NULL; } -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; + return ecs_strbuf_get(&buf); } +#endif + /** - * @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. + * @file json/serialize_type_info.c + * @brief Serialize type (reflection) information to JSON. */ +#ifdef FLECS_JSON + 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->column; - 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]); - } +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; + 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])); } - return r; } +} - return -1; +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 -int32_t flecs_type_offset_search( - int32_t offset, - ecs_id_t id, - ecs_id_t *ids, +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_id_t *id_out) + ecs_strbuf_t *str) { - 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); + ecs_strbuf_list_appendstr(str, "\"array\""); - 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; - } + 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 -bool flecs_type_can_inherit_id( +int json_typeinfo_ser_array_type( const ecs_world_t *world, - const ecs_table_t *table, - const ecs_id_record_t *idr, - ecs_id_t id) + ecs_entity_t type, + ecs_strbuf_t *str) { - 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; - } - } + 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 true; + + return 0; +error: + return -1; } static -int32_t flecs_type_search_relation( +int json_typeinfo_ser_vector( 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_entity_t type, + ecs_strbuf_t *str) { - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t count = type.count; + const EcsVector *arr = ecs_get(world, type, EcsVector); + ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); - 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_strbuf_list_appendstr(str, "\"vector\""); + + flecs_json_next(str); + if (json_typeinfo_ser_type(world, arr->type, str)) { + goto error; } - 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; + return 0; +error: + return -1; +} - if (!flecs_type_can_inherit_id(world, table, idr, id)) { - 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); - if (!idr_r) { - idr_r = flecs_id_record_get(world, rel); - if (!idr_r) { - return -1; - } + 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_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); + ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); + if (quantity) { + flecs_json_memberl(str, "quantity"); + flecs_json_path(str, world, quantity); } - 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); + return 0; +} - 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; - } +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); +} - 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; - } - } +/* 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 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); } - r_column = flecs_type_offset_search( - r_column + 1, rel, ids, count, &id_r); + flecs_json_object_pop(str); } } + flecs_json_array_pop(str); + + return 0; +error: return -1; } -int32_t flecs_search_relation_w_idr( +/* Iterate over a slice of the type ops array */ +static +int json_typeinfo_ser_type_ops( 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) + ecs_meta_type_op_t *ops, + int32_t op_count, + ecs_strbuf_t *str, + const EcsStruct *st) { - if (!table) return -1; + const EcsStruct *stack[64] = {st}; + int32_t sp = 1; - ecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + for (int i = 0; i < op_count; i ++) { + ecs_meta_type_op_t *op = &ops[i]; - flags = flags ? flags : (EcsSelf|EcsUp); + if (op != ops) { + if (op->name) { + flecs_json_member(str, op->name); + } + } - if (!idr) { - idr = flecs_query_id_record_get(world, id); - if (!idr) { - return -1; + 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; } - } - 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); + 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 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); } } - 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; + return 0; +error: + return -1; } -int32_t ecs_search_relation( +static +int json_typeinfo_ser_type( 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_entity_t type, + ecs_strbuf_t *buf) { - 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); + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + ecs_strbuf_appendch(buf, '0'); + return 0; } - ecs_id_record_t *idr = flecs_query_id_record_get(world, id); - if (!idr) { - return -1; + const EcsMetaTypeSerialized *ser = ecs_get( + world, type, EcsMetaTypeSerialized); + if (!ser) { + ecs_strbuf_appendch(buf, '0'); + return 0; } - 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); + 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 result; + return json_typeinfo_ser_type_ops(world, ops, count, buf, st); } -int32_t flecs_search_w_idr( +int ecs_type_info_to_json_buf( const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - ecs_id_t *id_out, - ecs_id_record_t *idr) + ecs_entity_t type, + ecs_strbuf_t *buf) { - 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); + return json_typeinfo_ser_type(world, type, buf); } -int32_t ecs_search( +char* ecs_type_info_to_json( const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - ecs_id_t *id_out) + ecs_entity_t type) { - if (!table) return -1; - - ecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_t str = ECS_STRBUF_INIT; - ecs_id_record_t *idr = flecs_query_id_record_get(world, id); - if (!idr) { - return -1; + if (ecs_type_info_to_json_buf(world, type, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; } - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - return flecs_type_search(table, id, idr, ids, id_out, 0); + return ecs_strbuf_get(&str); } -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); - } +#endif - if (!table) return -1; +/** + * @file meta/api.c + * @brief API for creating entities with reflection data. + */ - 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); -} +/** + * @file meta/meta.h + * @brief Private functions for meta addon. + */ -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; +#ifndef FLECS_META_PRIVATE_H +#define FLECS_META_PRIVATE_H - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return 0; - } - int32_t i = tr->column, 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); +#ifdef FLECS_META - 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; -} +void ecs_meta_type_serialized_init( + ecs_iter_t *it); -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; - } +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr); - int32_t depth_offset = 0; - if (table->flags & EcsTableHasTarget) { - if (ecs_table_get_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_meta_type_op_kind_t flecs_meta_primitive_to_op_kind( + ecs_primitive_kind_t kind); - 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]); - } - } +bool flecs_unit_validate( + ecs_world_t *world, + ecs_entity_t t, + EcsUnit *data); - return flecs_relation_depth_walk(world, idr, table, table) + depth_offset; -} +#endif + +#endif -/** - * @file observer.h - * @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 +#ifdef FLECS_META static -ecs_entity_t flecs_get_observer_event( - ecs_term_t *term, - ecs_entity_t event) +bool flecs_type_is_number( + ecs_world_t *world, + ecs_entity_t type) { - /* If operator is Not, reverse the event */ - if (term->oper == EcsNot) { - if (event == EcsOnAdd) { - event = EcsOnRemove; - } else if (event == EcsOnRemove) { - event = EcsOnAdd; - } + const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); + if (!p) { + return false; } - return event; -} + 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; -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; + case EcsBool: + case EcsByte: + case EcsUPtr: + case EcsIPtr: + case EcsString: + case EcsEntity: + return false; + default: + ecs_abort(ECS_INVALID_PARAMETER, NULL); + return false; } - return 0; } -static -void flecs_inc_observer_count( +ecs_entity_t ecs_primitive_init( ecs_world_t *world, - ecs_entity_t event, - ecs_event_record_t *evt, - ecs_id_t id, - int32_t value) + const ecs_primitive_desc_t *desc) { - 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_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - 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_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } - 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; - } - } + ecs_set(world, t, EcsPrimitive, { desc->kind }); - flecs_event_id_record_remove(evt, id); - ecs_os_free(idt); - } + flecs_resume_readonly(world, &rs); + return t; } -static -void flecs_register_observer_for_id( +ecs_entity_t ecs_enum_init( ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer, - size_t offset) + const ecs_enum_desc_t *desc) { - ecs_id_t term_id = observer->register_id; - ecs_term_t *term = &observer->filter.terms[0]; - ecs_entity_t trav = term->src.trav; + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - int i; - for (i = 0; i < observer->event_count; i ++) { - ecs_entity_t event = flecs_get_observer_event( - term, observer->events[i]); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } - /* Get observers for event */ - ecs_event_record_t *er = flecs_event_record_ensure(observable, event); - ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_add(world, t, EcsEnum); - /* 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_entity_t old_scope = ecs_set_scope(world, t); - 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); + 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; + } - flecs_inc_observer_count(world, event, er, term_id, 1); - if (trav) { - flecs_inc_observer_count(world, event, er, - ecs_pair(trav, EcsWildcard), 1); + 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}); } } -} -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; + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); - 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)); + if (i == 0) { + ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } + + return t; } -static -void flecs_unregister_observer_for_id( +ecs_entity_t ecs_bitmask_init( ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer, - size_t offset) + const ecs_bitmask_desc_t *desc) { - ecs_id_t term_id = observer->register_id; - ecs_term_t *term = &observer->filter.terms[0]; - ecs_entity_t trav = term->src.trav; + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - int i; - for (i = 0; i < observer->event_count; i ++) { - ecs_entity_t event = flecs_get_observer_event( - term, observer->events[i]); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } - /* Get observers for event */ - ecs_event_record_t *er = flecs_event_record_get(observable, event); - ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_add(world, t, EcsBitmask); - /* 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_entity_t old_scope = ecs_set_scope(world, t); - 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); + 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; } - flecs_inc_observer_count(world, event, er, term_id, -1); - if (trav) { - flecs_inc_observer_count(world, event, er, - ecs_pair(trav, EcsWildcard), -1); + 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; } -static -void flecs_unregister_observer( +ecs_entity_t ecs_array_init( ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer) + const ecs_array_desc_t *desc) { - 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_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_term_t *term = &observer->filter.terms[0]; - ecs_flags32_t flags = term->src.flags; + ecs_set(world, t, EcsArray, { + .type = desc->type, + .count = desc->count + }); - 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)); - } + flecs_resume_readonly(world, &rs); + + return t; } -static -bool flecs_ignore_observer( - ecs_observer_t *observer, - ecs_table_t *table, - int32_t evtx) +ecs_entity_t ecs_vector_init( + ecs_world_t *world, + const ecs_vector_desc_t *desc) { - ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - int32_t *last_event_id = observer->last_event_id; - if (last_event_id && last_event_id[0] == evtx) { - return true; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags; + ecs_set(world, t, EcsVector, { + .type = desc->type + }); - bool result = (table_flags & EcsTableIsPrefab) && - !(filter_flags & EcsFilterMatchPrefab); - result = result || ((table_flags & EcsTableIsDisabled) && - !(filter_flags & EcsFilterMatchDisabled)); + flecs_resume_readonly(world, &rs); - return result; + return t; } static -bool flecs_is_simple_result( - ecs_iter_t *it) +bool flecs_member_range_overlaps( + const ecs_member_value_range_t *range, + const ecs_member_value_range_t *with) { - return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0); + 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; } -static -void flecs_observer_invoke( +ecs_entity_t ecs_struct_init( ecs_world_t *world, - ecs_iter_t *it, - ecs_observer_t *observer, - ecs_iter_action_t callback, - int32_t term_index, - bool simple_result) + const ecs_struct_desc_t *desc) { - ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - 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_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - ecs_log_push_3(); + ecs_entity_t old_scope = ecs_set_scope(world, t); - world->info.observers_ran_frame ++; + 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; + } - 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); - } + if (!m_desc->name) { + ecs_err("member %d of struct '%s' does not have a name", i, + ecs_get_name(world, t)); + goto error; + } - 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 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 [%d..%d]", + 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 [%d..%d]", + 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; } - 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; + 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 [%d..%d]", + 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); } - it->entities = entities; - it->count = count; } - ecs_log_pop_3(); -} + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); -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 (i == 0) { + ecs_err("struct '%s' has no members", ecs_get_name(world, t)); + goto error; + } - if (ecs_should_log_3()) { - char *path = ecs_get_fullpath(it->world, it->system); - ecs_dbg_3("observer %s", path); - ecs_os_free(path); + if (!ecs_has(world, t, EcsStruct)) { + goto error; } - ecs_log_push_3(); - flecs_observer_invoke(it->real_world, it, o, o->callback, 0, - flecs_is_simple_result(it)); - ecs_log_pop_3(); + return t; +error: + flecs_resume_readonly(world, &rs); + if (t) { + ecs_delete(world, t); + } + return 0; } -static -void flecs_uni_observer_invoke( +ecs_entity_t ecs_opaque_init( ecs_world_t *world, - ecs_observer_t *observer, - ecs_iter_t *it, - ecs_table_t *table, - ecs_entity_t trav, - int32_t evtx, - bool simple_result) + const ecs_opaque_desc_t *desc) { - ecs_filter_t *filter = &observer->filter; - ecs_term_t *term = &filter->terms[0]; - if (flecs_ignore_observer(observer, table, evtx)) { - return; - } + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); - if (trav && term->src.trav != trav) { - return; - } + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - 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->terms = term; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } - ecs_entity_t event = it->event; - it->event = flecs_get_observer_event(term, event); + ecs_set_ptr(world, t, EcsOpaque, &desc->type); - 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); - } + flecs_resume_readonly(world, &rs); - it->event = event; + return t; } -void flecs_observers_invoke( +ecs_entity_t ecs_unit_init( ecs_world_t *world, - ecs_map_t *observers, - ecs_iter_t *it, - ecs_table_t *table, - ecs_entity_t trav, - int32_t evtx) + const ecs_unit_desc_t *desc) { - 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, evtx, simple_result); - } + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - ecs_table_unlock(it->world, table); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } -} -static -bool flecs_multi_observer_invoke(ecs_iter_t *it) { - ecs_observer_t *o = it->ctx; - ecs_world_t *world = it->real_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; + } - if (o->last_event_id[0] == world->event_id) { - /* Already handled this event */ - return false; + ecs_add_pair(world, t, EcsQuantity, desc->quantity); + } else { + ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); } - o->last_event_id[0] = world->event_id; - - ecs_iter_t user_it = *it; - user_it.field_count = o->filter.field_count; - 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; + 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); - flecs_iter_init(it->world, &user_it, flecs_iter_cache_all); - user_it.flags |= (it->flags & EcsIterTableOnly); + if (!flecs_unit_validate(world, t, value)) { + goto error; + } - 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]; + ecs_modified(world, t, EcsUnit); - int32_t column = it->columns[0]; - if (term->oper == EcsNot) { - table = it->other_table; - prev_table = it->table; + flecs_resume_readonly(world, &rs); + return t; +error: + if (t) { + ecs_delete(world, t); } + flecs_resume_readonly(world, &rs); + return 0; +} - if (!table) { - table = &world->store.root; - } - if (!prev_table) { - prev_table = &world->store.root; - } +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); - if (column < 0) { - column = -column; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - 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; + ecs_set(world, t, EcsUnitPrefix, { + .symbol = ECS_CONST_CAST(char*, desc->symbol), + .translation = desc->translation + }); - 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; - } - } + flecs_resume_readonly(world, &rs); - /* 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); - } + return t; +} - flecs_iter_populate_data(world, &user_it, it->table, it->offset, - it->count, user_it.ptrs); +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); - 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; + ecs_entity_t t = ecs_entity_init(world, desc); + if (!t) { + return 0; } -done: - ecs_iter_fini(&user_it); - return false; -} + ecs_add_id(world, t, EcsQuantity); -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; - } -} + flecs_resume_readonly(world, &rs); -static -void flecs_default_multi_observer_run_callback(ecs_iter_t *it) { - flecs_multi_observer_invoke(it); + return t; } -/* 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; - } -} +#endif -/* 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; +/** + * @file meta/cursor.c + * @brief API for assigning values of runtime types with reflection. + */ - 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); - } -} +#include + +#ifdef FLECS_META static -void flecs_uni_observer_trigger_existing( - ecs_world_t *world, - ecs_observer_t *observer) +const char* flecs_meta_op_kind_str( + ecs_meta_type_op_kind_t kind) { - ecs_iter_action_t callback = observer->callback; - - /* 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]; - callback(&it); - } + switch(kind) { - ecs_iter_fini(&it); + 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 EcsOpScope: return "Scope"; + default: return "<< invalid kind >>"; } } +/* Get current scope */ static -void flecs_multi_observer_yield_existing( - ecs_world_t *world, - ecs_observer_t *observer) +ecs_meta_scope_t* flecs_meta_cursor_get_scope( + const ecs_meta_cursor_t *cursor) { - ecs_run_action_t run = observer->run; - if (!run) { - run = flecs_default_multi_observer_run_callback; - } - - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + return ECS_CONST_CAST(ecs_meta_scope_t*, &cursor->scope[cursor->depth]); +error: + return NULL; +} - int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); - if (pivot_term < 0) { - return; +/* 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]; +} - /* 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.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]; - run(&it); - world->event_id ++; - } - } +/* 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 -int flecs_uni_observer_init( - ecs_world_t *world, - ecs_observer_t *observer, - const ecs_observer_desc_t *desc) +const EcsComponent* get_ecs_component( + const ecs_world_t *world, + ecs_meta_scope_t *scope) { - 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; + const EcsComponent *comp = scope->comp; + if (!comp) { + comp = scope->comp = ecs_get(world, scope->type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); } - observer->register_id = flecs_from_public_id(world, term->id); - term->field_index = desc->term_index; + return comp; +} - 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; - } - } - } +/* 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; +} - flecs_uni_observer_register(world, observer->observable, observer); +static +int32_t get_elem_count( + ecs_meta_scope_t *scope) +{ + const EcsOpaque *opaque = scope->opaque; - if (desc->yield_existing) { - flecs_uni_observer_trigger_existing(world, observer); + if (scope->vector) { + return ecs_vec_count(scope->vector); + } else if (opaque && opaque->count) { + return flecs_uto(int32_t, opaque->count(scope[-1].ptr)); } - return 0; + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + return op->count; } +/* Get pointer to current field/element */ static -int flecs_multi_observer_init( - ecs_world_t *world, - ecs_observer_t *observer, - const ecs_observer_desc_t *desc) +ecs_meta_type_op_t* flecs_meta_cursor_get_ptr( + const ecs_world_t *world, + ecs_meta_scope_t *scope) { - /* 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; + 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; - /* 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); + 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; - 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; + 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; } } - if (filter->flags & EcsFilterMatchPrefab) { - child_desc.filter.flags |= EcsFilterMatchPrefab; - } - if (filter->flags & EcsFilterMatchDisabled) { - child_desc.filter.flags |= EcsFilterMatchDisabled; + 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; } - /* Create observers as children of observer */ - ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity); + 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 + }; - for (i = 0; i < term_count; i ++) { - if (filter->terms[i].src.flags & EcsFilter) { - continue; - } + return 0; +} - ecs_term_t *term = &child_desc.filter.terms[0]; - child_desc.term_index = filter->terms[i].field_index; - *term = filter->terms[i]; +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_oper_kind_t oper = term->oper; - ecs_id_t id = term->id; + ecs_meta_cursor_t result = { + .world = world, + .valid = true + }; - /* 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; + if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) != 0) { + result.valid = false; + } - /* 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; - } + return result; +error: + return (ecs_meta_cursor_t){ 0 }; +} - term->first.name = NULL; - term->first.id = ti_ids[ti]; - term->id = ti_ids[ti]; +void* ecs_meta_get_ptr( + ecs_meta_cursor_t *cursor) +{ + return flecs_meta_cursor_get_ptr(cursor->world, + flecs_meta_cursor_get_scope(cursor)); +} - if (ecs_observer_init(world, &child_desc) == 0) { - goto error; - } - } - continue; - } +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); - /* Single component observers never use OR */ - if (oper == EcsOr) { - term->oper = EcsAnd; - } + if (scope->is_collection) { + scope->elem_cur ++; + scope->op_cur = 0; - /* 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 (scope->opaque) { + return 0; } - if (optional_only) { - break; + if (scope->elem_cur >= get_elem_count(scope)) { + ecs_err("out of collection bounds (%d)", scope->elem_cur); + return -1; } + return 0; } - ecs_set_scope(world, old_scope); + scope->op_cur += op->op_count; - if (desc->yield_existing) { - flecs_multi_observer_yield_existing(world, observer); + if (scope->op_cur >= scope->op_count) { + ecs_err("out of bounds"); + return -1; } - return 0; -error: - return -1; + return 0; } -ecs_entity_t ecs_observer_init( - ecs_world_t *world, - const ecs_observer_desc_t *desc) +int ecs_meta_elem( + ecs_meta_cursor_t *cursor, + int32_t elem) { - 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); + 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; } - 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; - } + scope->elem_cur = elem; + scope->op_cur = 0; - /* Observer must have at least one term */ - ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL); + 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; +} - poly->poly = observer; +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_observable_t *observable = desc->observable; - if (!observable) { - observable = ecs_get_observable(world); - } + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + scope = flecs_meta_cursor_restore_scope(cursor, scope); - 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; + ecs_hashmap_t *members = scope->members; + const ecs_world_t *world = cursor->world; - /* 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 (!members) { + ecs_err("cannot move to member '%s' for non-struct type", name); + return -1; + } - if (event == EcsMonitor) { - /* Monitor event must be first and last event */ - ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); + 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; + } - observer->events[0] = EcsOnAdd; - observer->events[1] = EcsOnRemove; - observer->event_count ++; - observer->is_monitor = true; - } else { - observer->events[i] = event; - } + scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); - observer->event_count ++; + 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); } + } - /* 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; - } + return 0; +} - 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; - } - } +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); - if (ecs_get_name(world, entity)) { - ecs_trace("#[green]observer#[reset] %s created", - ecs_get_name(world, entity)); - } - } else { - ecs_observer_t *observer = ecs_poly(poly->poly, ecs_observer_t); + int32_t prev_depth = cursor->depth; + int dotcount = 0; - if (desc->run) { - observer->run = desc->run; - } - if (desc->callback) { - observer->callback = desc->callback; + 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 (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 (dotcount) { + ecs_meta_push(cursor); } - 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 (ecs_meta_member(cursor, token)) { + goto error; } - if (desc->binding_ctx_free) { - observer->binding_ctx_free = desc->binding_ctx_free; + + if (!ptr[0]) { + break; } + + ptr ++; /* Skip . */ + + dotcount ++; } - ecs_poly_modified(world, entity, ecs_observer_t); + cur_scope = flecs_meta_cursor_get_scope(cursor); + if (dotcount) { + cur_scope->prev_depth = prev_depth; + } - return entity; -error: - ecs_delete(world, entity); 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 } -void* ecs_get_observer_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_get_observer_binding_ctx( - const ecs_world_t *world, - ecs_entity_t observer) +int ecs_meta_push( + ecs_meta_cursor_t *cursor) { - 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; - } -} + 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; -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 */ + if (cursor->depth == 0) { + if (!cursor->is_primitive_scope) { + if (op->kind > EcsOpScope) { + cursor->is_primitive_scope = true; + return 0; + } } } - /* Cleanup filters */ - ecs_filter_fini(&observer->filter); + void *ptr = flecs_meta_cursor_get_ptr(world, scope); + cursor->depth ++; + ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, + ECS_INVALID_PARAMETER, NULL); - /* Cleanup context */ - if (observer->ctx_free) { - observer->ctx_free(observer->ctx); - } + ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); - if (observer->binding_ctx_free) { - observer->binding_ctx_free(observer->binding_ctx); - } + /* 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 + }; - ecs_poly_free(observer, ecs_observer_t); -} + /* With 'is_inline_array' set to true we ensure that we can never push + * the same inline array twice */ + return 0; + } -/** - * @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. - */ + /* 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; + } -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; + /* 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; + } - if (next) { - next->prev = prev; - } - if (prev) { - prev->next = next; + /* 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; } - cache->empty_tables.count -= !!elem->empty; - cache->tables.count -= !elem->empty; + /* 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; + } - 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; + const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; } -} -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; + /* Vector push */ + case EcsOpVector: { + next_scope->vector = ptr; + if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { + goto error; } - } - - elem->next = NULL; - elem->prev = last; - if (last) { - last->next = elem; + const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); + next_scope->type = type_ptr->type; + next_scope->is_collection = true; + break; } -} - -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); -} + /* 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); -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache) -{ - return ecs_map_count(&cache->index) == 0; -} + /* Check what kind of type the opaque type represents */ + switch(mtype_ptr->kind) { -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); + /* Opaque vector support */ + case EcsVectorType: { + const EcsVector *vt = ecs_get(world, type_ptr->as_type, EcsVector); + next_scope->type = vt->type; - bool empty; - if (!table) { - empty = false; - } else { - empty = ecs_table_count(table) == 0; - } + /* Push the element type of the vector type */ + if (flecs_meta_cursor_push_type( + world, next_scope, vt->type, NULL) != 0) + { + goto error; + } - result->cache = cache; - result->table = (ecs_table_t*)table; - result->empty = empty; + /* 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; + } - flecs_table_cache_list_insert(cache, result); + /* Opaque array support */ + case EcsArrayType: { + const EcsArray *at = ecs_get(world, type_ptr->as_type, EcsArray); + next_scope->type = at->type; - if (table) { - ecs_map_insert_ptr(&cache->index, table->id, result); - } + /* Push the element type of the array type */ + if (flecs_meta_cursor_push_type( + world, next_scope, at->type, NULL) != 0) + { + goto error; + } - ecs_assert(empty || cache->tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!empty || cache->empty_tables.first != NULL, - ECS_INTERNAL_ERROR, NULL); -} + /* Arrays are always a fixed size */ + next_scope->is_empty_scope = false; + next_scope->is_collection = true; + break; + } -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); + /* 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; + } - ecs_table_cache_hdr_t *old = *r; - ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); + /* 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; + } - 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; + next_scope->ptr = ptr; + next_scope->opaque = type_ptr; + break; } - if (cache->empty_tables.first == old) { - cache->empty_tables.first = elem; - } - if (cache->empty_tables.last == old) { - cache->empty_tables.last = elem; + 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: { + char *path = ecs_get_fullpath(world, scope->type); + ecs_err("invalid push for type '%s'", path); + ecs_os_free(path); + goto error; } - if (cache->tables.first == old) { - cache->tables.first = elem; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } - if (cache->tables.last == old) { - cache->tables.last = elem; + + if (scope->is_collection && !scope->opaque) { + next_scope->ptr = ECS_OFFSET(next_scope->ptr, + scope->elem_cur * get_size(world, scope)); } - *r = elem; - elem->prev = prev; - elem->next = next; + return 0; +error: + return -1; } -void* ecs_table_cache_get( - const ecs_table_cache_t *cache, - const ecs_table_t *table) +int ecs_meta_pop( + ecs_meta_cursor_t *cursor) { - 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; + if (cursor->is_primitive_scope) { + cursor->is_primitive_scope = false; + return 0; } -} - -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); + 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; + } - return elem; -} + 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); -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); + if (!scope->is_inline_array) { + if (op->kind == EcsOpPush) { + next_scope->op_cur += op->op_count - 1; - ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, - ecs_table_cache_hdr_t, table->id); - if (!elem) { - return false; - } + /* 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); - if (elem->empty == empty) { - return false; + /* 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); + } + } else { + /* Make sure that this was an inline array */ + ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); } - flecs_table_cache_list_remove(cache, elem); - elem->empty = empty; - flecs_table_cache_list_insert(cache, elem); + return 0; +} - return true; +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; } -bool flecs_table_cache_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) +ecs_entity_t ecs_meta_get_type( + const ecs_meta_cursor_t *cursor) { - 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; + 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; } -bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) +ecs_entity_t ecs_meta_get_unit( + const ecs_meta_cursor_t *cursor) { - 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; + 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; } -bool flecs_table_cache_all_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) +const char* ecs_meta_get_member( + const ecs_meta_cursor_t *cursor) { - 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_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_table_cache_hdr_t* _flecs_table_cache_next( - ecs_table_cache_iter_t *it) +ecs_entity_t ecs_meta_get_member_id( + const ecs_meta_cursor_t *cursor) { - ecs_table_cache_hdr_t *next = it->next; - if (!next) { - next = it->next_list; - it->next_list = NULL; - if (!next) { - return false; - } + 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; } - it->cur = next; - it->next = next->next; - return next; + 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; } -/** - * @file os_api.h - * @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. - */ +/* 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}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, INT32_MAX} +}; -#include -#include +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}, + [EcsOpEnum] = {0, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; -void ecs_os_api_impl(ecs_os_api_t *api); +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}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; -static bool ecs_os_api_initialized = false; -static bool ecs_os_api_initializing = false; -static int ecs_os_api_init_count = 0; +#define set_T(T, ptr, value)\ + ((T*)ptr)[0] = ((T)value) -#ifndef __EMSCRIPTEN__ -ecs_os_api_t ecs_os_api = { - .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, - .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ -}; -#else -/* Disable colors by default for emscripten */ -ecs_os_api_t ecs_os_api = { - .flags_ = EcsOsApiHighResolutionTimer, - .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ -}; -#endif +#define case_T(kind, T, dst, src)\ +case kind:\ + set_T(T, dst, src);\ + break -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; +#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 -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; - } -} +#define cases_T_float(dst, src)\ + case_T(EcsOpF32, ecs_f32_t, dst, src);\ + case_T(EcsOpF64, ecs_f64_t, dst, src) -ecs_os_api_t ecs_os_get_api(void) { - return ecs_os_api; -} +#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) -void ecs_os_init(void) +#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(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 (!ecs_os_api_initialized) { - ecs_os_set_api_defaults(); - } - - if (!(ecs_os_api_init_count ++)) { - if (ecs_os_api.init_) { - ecs_os_api.init_(); - } + 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); } } -void ecs_os_fini(void) { - if (!--ecs_os_api_init_count) { - if (ecs_os_api.fini_) { - ecs_os_api.fini_(); +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; } } -} - -/* 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 + /* 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"); + } -#if HAVE_EXECINFO -#include -#define ECS_BT_BUF_SIZE 100 + return 0; +error: + return -1; +} -void flecs_dump_backtrace( - FILE *stream) +int ecs_meta_set_char( + ecs_meta_cursor_t *cursor, + char value) { - int nptrs; - void *buffer[ECS_BT_BUF_SIZE]; - char **strings; - - nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); + 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); - strings = backtrace_symbols(buffer, nptrs); - if (strings == NULL) { - return; + 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; + } } - - for (int j = 1; j < nptrs; j++) { - fprintf(stream, "%s\n", strings[j]); + /* 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: + flecs_meta_conversion_error(cursor, op, "char"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } - free(strings); -} -#else -void flecs_dump_backtrace( - FILE *stream) -{ - (void)stream; + return 0; +error: + return -1; } -#endif -#undef HAVE_EXECINFO_H -static -void flecs_log_msg( - int32_t level, - const char *file, - int32_t line, - const char *msg) +int ecs_meta_set_int( + ecs_meta_cursor_t *cursor, + int64_t value) { - FILE *stream; - if (level >= 0) { - stream = stdout; - } else { - stream = stderr; - } - - 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); - time_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; + 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 (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); + 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; } } - - 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); + /* 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; } - - 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); + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } - 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] = '+'; - } + return 0; +error: + return -1; +} - indent[i * 2] = '\0'; +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); - fputs(indent, stream); + 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"); + } - if (level < 0) { - if (file) { - const char *file_ptr = strrchr(file, '/'); - if (!file_ptr) { - file_ptr = strrchr(file, '\\'); - } + return 0; +error: + return -1; +} - if (file_ptr) { - file = file_ptr + 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); - fputs(file, stream); - fputs(": ", stream); + switch(op->kind) { + case EcsOpBool: + if (ECS_EQZERO(value)) { + set_T(bool, ptr, false); + } else { + set_T(bool, ptr, true); } - - if (line) { - fprintf(stream, "%d: ", line); + 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; } } - - fputs(msg, stream); - - fputs("\n", stream); - - if (level == -4) { - flecs_dump_backtrace(stream); + /* 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"); } -} -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); - } + return 0; +error: + return -1; } -void ecs_os_trace( - const char *file, - int32_t line, - const char *msg) +int ecs_meta_set_value( + ecs_meta_cursor_t *cursor, + const ecs_value_t *value) { - if (ecs_os_api.log_) { - ecs_os_api.log_(0, file, line, msg); + 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; } -} -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); + 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); + 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; } -void ecs_os_err( - const char *file, - int32_t line, - const char *msg) +static +int flecs_meta_add_bitmask_constant( + ecs_meta_cursor_t *cursor, + ecs_meta_type_op_t *op, + void *out, + const char *value) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-3, file, line, msg); + ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_os_strcmp(value, "0")) { + return 0; } -} -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); + 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; } -} -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; + 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; + } - assert(sec < UINT32_MAX); - assert((now - sec * 1000000000) < UINT32_MAX); + *(ecs_u32_t*)out |= v[0]; - time->sec = (uint32_t)sec; - time->nanosec = (uint32_t)(now - sec * 1000000000); + return 0; } 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); -} +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]; -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); -} + const char *prev = value, *ptr = value; -static -void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + *(ecs_u32_t*)out = 0; - 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); + 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; } - - return realloc(ptr, (size_t)size); -} -static -void ecs_os_api_free(void *ptr) { - if (ptr) { - ecs_os_linc(&ecs_os_api_free_count); + if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { + return -1; } - free(ptr); + + return 0; } 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; +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; } -void ecs_os_strset(char **str, const char *value) { - char *old = str[0]; - str[0] = ecs_os_strdup(value); - ecs_os_free(old); -} +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); -/* 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; + 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, atol(value)); + break; + case EcsOpIPtr: + case EcsOpUPtr: + set_T(ecs_iptr_t, ptr, atol(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; + } - return base; + 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 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; } -static -char* ecs_os_api_module_to_dl(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; +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); - /* Best guess, use module name with underscores + OS library extension */ - char *file_base = module_file_base(module, '_'); + ecs_size_t len = ecs_os_strlen(value); + if (value[0] != '\"' || value[len - 1] != '\"') { + ecs_err("invalid string literal '%s'", value); + goto error; + } -# 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 + switch(op->kind) { + case EcsOpChar: + set_T(ecs_char_t, ptr, value[1]); + break; - ecs_os_free(file_base); + 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: + len -= 2; - return ecs_strbuf_get(&lib); -} + char *result = ecs_os_malloc(len + 1); + ecs_os_memcpy(result, value + 1, len); + result[len] = '\0'; -static -char* ecs_os_api_module_to_etc(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; + if (ecs_meta_set_string(cursor, result)) { + ecs_os_free(result); + return -1; + } - /* Best guess, use module name with dashes + /etc */ - char *file_base = module_file_base(module, '-'); + ecs_os_free(result); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendlit(&lib, "/etc"); + return 0; +error: + return -1; +} - ecs_os_free(file_base); +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 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 ecs_strbuf_get(&lib); + return 0; +error: + return -1; } -void ecs_os_set_api_defaults(void) +int ecs_meta_set_null( + ecs_meta_cursor_t *cursor) { - /* Don't overwrite if already initialized */ - if (ecs_os_api_initialized != 0) { - return; + 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; + } } - - if (ecs_os_api_initializing != 0) { - return; + /* 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: + flecs_meta_conversion_error(cursor, op, "null"); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; } - 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; - } + return 0; +error: + return -1; +} - if (!ecs_os_api.module_to_etc_) { - ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; +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 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; } - ecs_os_api.abort_ = abort; +error: + return 0; +} -# 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 +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: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); + break; + } - ecs_os_api_initializing = false; +error: + return 0; } -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); +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 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; } -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); +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 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; } -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); +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 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; } -bool ecs_os_has_time(void) { - return - (ecs_os_api.get_time_ != NULL) && - (ecs_os_api.sleep_ != NULL) && - (ecs_os_api.now_ != NULL); +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); } -bool ecs_os_has_logging(void) { - return (ecs_os_api.log_ != NULL); +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: + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); + break; + } +error: + return 0; } -bool ecs_os_has_dl(void) { - return - (ecs_os_api.dlopen_ != NULL) && - (ecs_os_api.dlproc_ != NULL) && - (ecs_os_api.dlclose_ != NULL); +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: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); + break; + } +error: + return 0; } -bool ecs_os_has_modules(void) { - return - (ecs_os_api.module_to_dl_ != NULL) && - (ecs_os_api.module_to_etc_ != NULL); +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); } -#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 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. + * @file meta/meta.c + * @brief Meta addon. */ -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; - } -} +#ifdef FLECS_META -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); -} +/* ecs_string_t lifecycle */ -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); +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); +}) - 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); - } - } +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; +}) - return group; -} +static ECS_DTOR(ecs_string_t, ptr, { + ecs_os_free(*(ecs_string_t*)ptr); + *(ecs_string_t*)ptr = NULL; +}) -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); -} +/* EcsMetaTypeSerialized lifecycle */ -static -uint64_t flecs_query_default_group_by( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) +void ecs_meta_dtor_serialized( + EcsMetaTypeSerialized *ptr) { - (void)ctx; - - ecs_id_t match; - if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { - return ecs_pair_second(world, match); + 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); + } } - return 0; + + ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_type_op_t); } -/* 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); +static ECS_COPY(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); - 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; + dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_type_op_t); - /* Find closest smaller group id */ - while (ecs_map_next(&it)) { - id = ecs_map_key(&it); - if (id >= group_id) { - continue; + 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); } + } +}) - list = ecs_map_ptr(&it); - if (!list->last) { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - continue; - } +static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { + ecs_meta_dtor_serialized(dst); + dst->ops = src->ops; + src->ops = (ecs_vec_t){0}; +}) - if (!closest_list || ((group_id - id) < (group_id - closest_id))) { - closest_id = id; - closest_list = list; - } - } +static ECS_DTOR(EcsMetaTypeSerialized, ptr, { + ecs_meta_dtor_serialized(ptr); +}) - if (closest_list) { - return closest_list->last; - } else { - return NULL; /* Group should be first in query */ + +/* 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); } -/* 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; +static ECS_COPY(EcsStruct, dst, src, { + flecs_struct_dtor(dst); - /* 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); + dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); - 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); + ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); + int32_t m, count = ecs_vec_count(&dst->members); - /* 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; - } + for (m = 0; m < count; m ++) { + members[m].name = ecs_os_strdup(members[m].name); } -} +}) -/* 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) +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) { - if (query->group_by) { - return flecs_query_get_group(query, match->group_id); - } else { - return &query->list; + 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); } -/* 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) +static void flecs_constants_copy( + ecs_map_t *dst, + const ecs_map_t *src) { - if (query->group_by) { - return flecs_query_ensure_group(query, match->group_id); - } else { - return &query->list; + 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; } } -/* 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; +static ECS_COPY(EcsEnum, dst, src, { + flecs_constants_dtor(&dst->constants); + flecs_constants_copy(&dst->constants, &src->constants); +}) - 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); +static ECS_MOVE(EcsEnum, dst, src, { + flecs_constants_dtor(&dst->constants); + dst->constants = src->constants; + ecs_os_zeromem(&src->constants); +}) - ecs_query_table_list_t *list = flecs_query_get_node_list(query, match); +static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) - 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); +/* EcsBitmask lifecycle */ - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; - } +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); +}) - ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); - list->info.table_count --; +static ECS_MOVE(EcsBitmask, dst, src, { + flecs_constants_dtor(&dst->constants); + dst->constants = src->constants; + ecs_os_zeromem(&src->constants); +}) - if (query->group_by) { - uint64_t group_id = match->group_id; +static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) - /* 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 ++; +/* EcsUnit lifecycle */ - /* 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; - } +static void dtor_unit( + EcsUnit *ptr) +{ + ecs_os_free(ptr->symbol); +} - /* 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; - } - } +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; +}) - if (list) { - if (list->first == match) { - list->first = next; - } - if (list->last == match) { - list->last = prev; - } - } +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; - match->prev = NULL; - match->next = NULL; + src->symbol = NULL; + src->base = 0; + src->over = 0; + src->prefix = 0; + src->translation = (ecs_unit_translation_t){0}; +}) - query->match_count ++; -} +static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) -/* Add node to list */ -static -void flecs_query_insert_table_node( - ecs_query_t *query, - ecs_query_table_match_t *match) + +/* EcsUnitPrefix lifecycle */ + +static void dtor_unit_prefix( + EcsUnitPrefix *ptr) { - /* Node should not be part of an existing list */ - ecs_assert(match->prev == NULL && match->next == NULL, - ECS_INTERNAL_ERROR, NULL); + ecs_os_free(ptr->symbol); +} - /* 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); - } +static ECS_COPY(EcsUnitPrefix, dst, src, { + dtor_unit_prefix(dst); + dst->symbol = ecs_os_strdup(src->symbol); + dst->translation = src->translation; +}) - flecs_query_compute_group_id(query, match); +static ECS_MOVE(EcsUnitPrefix, dst, src, { + dtor_unit_prefix(dst); + dst->symbol = src->symbol; + dst->translation = src->translation; - 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); + src->symbol = NULL; + src->translation = (ecs_unit_translation_t){0}; +}) - ecs_query_table_match_t *last = list->last; - ecs_query_table_match_t *last_next = last->next; +static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) - match->prev = last; - match->next = last_next; - last->next = match; +/* Type initialization */ - if (last_next) { - last_next->prev = match; - } +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"; + } +} - list->last = match; +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); - 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; - } + 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 { - 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 (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 (query->group_by) { - list->info.table_count ++; - list->info.match_count ++; + 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; } - 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); + meta_type->kind = kind; + ecs_modified(world, type, EcsMetaType); - 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); + return 0; } +#define init_type_t(world, type, kind, T) \ + flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + static -ecs_query_table_match_t* flecs_query_cache_add( - ecs_world_t *world, - ecs_query_table_t *elem) +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) { - ecs_query_table_match_t *result = - flecs_bcalloc(&world->allocators.query_table_match); + member->member = entity; + member->type = type; + member->count = count; + member->unit = unit; + member->offset = offset; - 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; + if (!count) { + member->count = 1; } - return result; -} + ecs_os_strset(ECS_CONST_CAST(char**, &member->name), name); -typedef struct { - ecs_table_t *table; - int32_t *dirty_state; - int32_t column; -} table_dirty_state_t; + 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 -void flecs_query_get_dirty_state( - ecs_query_t *query, - ecs_query_table_match_t *match, - int32_t term, - table_dirty_state_t *out) +int flecs_add_member_to_struct( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t member, + EcsMember *m, + EcsMemberRanges *ranges) { - ecs_world_t *world = query->filter.world; - ecs_entity_t subject = match->sources[term]; - ecs_table_t *table; - int32_t column = -1; + 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 (!subject) { - table = match->table; - column = match->storage_columns[term]; + 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 { - table = ecs_get_table(world, subject); - int32_t ref_index = -match->columns[term] - 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; - column = ecs_table_type_to_storage_index(table, column); + if (ecs_has(world, m->type, EcsUnit)) { + unit = m->type; + m->unit = unit; } } - out->table = table; - out->column = column; - out->dirty_state = flecs_table_get_dirty_state(world, table); -} + EcsStruct *s = ecs_get_mut(world, type, EcsStruct); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); -/* 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; + /* 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; + } } - int32_t *monitor = flecs_balloc(&query->allocators.monitors); - monitor[0] = 0; + /* 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); - /* 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, t = -1, term_count = f->term_count; - table_dirty_state_t cur_dirty_state; + /* Reobtain members array in case it was reallocated */ + members = ecs_vec_first_t(&s->members, ecs_member_t); + count ++; + } - for (i = 0; i < term_count; i ++) { - if (t == f->terms[i].field_index) { - if (monitor[t + 1] != -1) { - continue; - } - } + bool explicit_offset = false; + if (m->offset) { + explicit_offset = true; + } - t = f->terms[i].field_index; - monitor[t + 1] = -1; + /* Compute member offsets and size & alignment of struct */ + ecs_size_t size = 0; + ecs_size_t alignment = 0; - 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 */ - } + if (!explicit_offset) { + for (i = 0; i < count; i ++) { + ecs_member_t *elem = &members[i]; - /* If term is not matched on this, don't track */ - if (!ecs_term_match_this(&f->terms[i])) { - continue; - } + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - int32_t column = match->columns[t]; - if (column == 0) { - continue; /* Don't track terms that aren't matched */ - } + /* 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; + } - flecs_query_get_dirty_state(query, match, t, &cur_dirty_state); - if (cur_dirty_state.column == -1) { - continue; /* Don't track terms that aren't stored */ - } + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; - monitor[t + 1] = 0; - } + if (!member_size || !member_alignment) { + char *path = ecs_get_fullpath(world, elem->type); + ecs_err("member '%s' has 0 size/alignment"); + ecs_os_free(path); + return -1; + } + member_size *= elem->count; + size = ECS_ALIGN(size, member_alignment); + elem->size = member_size; + elem->offset = size; - /* 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; - } - } + /* 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; + } - match->monitor = monitor; + size += member_size; - query->flags |= EcsQueryHasMonitor; + 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]; - return true; -} + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); -/* 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; + /* 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; } - } - 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_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; - table_dirty_state_t cur; - int32_t i, term_count = query->filter.term_count; - for (i = 0; i < term_count; i ++) { - int32_t t = query->filter.terms[i].field_index; - if (monitor[t + 1] == -1) { - continue; - } - - flecs_query_get_dirty_state(query, match, t, &cur); - if (cur.column < 0) { - continue; + if (!member_size || !member_alignment) { + char *path = ecs_get_fullpath(world, elem->type); + ecs_err("member '%s' has 0 size/alignment"); + ecs_os_free(path); + return -1; } - monitor[t + 1] = cur.dirty_state[cur.column + 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; } - 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 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 *tgt_mon = ecs_vec_first(&field->monitor); - int32_t t, tgt_count = ecs_vec_count(&field->monitor); - for (t = 0; t < tgt_count; t ++) { - tgt_mon[t].monitor = tgt_mon[t].table_state; - } - } + if (size == 0) { + ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); + return -1; } - query->prev_match_count = query->match_count; -} + if (alignment == 0) { + ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); + return -1; + } -/* 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); + /* Align struct size to struct alignment */ + size = ECS_ALIGN(size, alignment); - 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_modified(world, type, EcsStruct); - 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; + /* Do this last as it triggers the update of EcsMetaTypeSerialized */ + if (flecs_init_type(world, type, EcsStructType, size, alignment)) { + return -1; } - table_dirty_state_t cur; - flecs_query_get_dirty_state(query, match, term - 1, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + /* 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); - return monitor[term] != cur.dirty_state[cur.column + 1]; + type_mbr->type = type; + type_mbr->count = 1; + + ecs_modified(world, type, EcsMember); + } + + return 0; } -/* 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) +int flecs_add_constant_to_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum); - if (flecs_query_get_match_monitor(query, match)) { - return true; + /* 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)); + } } - 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; + /* 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; } - } - bool has_flat = false; - ecs_world_t *world = query->filter.world; - int32_t i, field_count = query->filter.field_count; - int32_t *storage_columns = match->storage_columns; - int32_t *columns = it ? it->columns : NULL; - if (!columns) { - columns = match->columns; + 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; } - 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 (column >= 0) { - /* owned component */ - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (mon != dirty_state[column + 1]) { - return 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; } - 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; - - /* Flattened fields are encoded by adding field_count to the column - * index of the parent component. */ - if (it && (column > field_count)) { - has_flat = true; } else { - 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->column; - column = ecs_table_type_to_storage_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; - } + if (c->value >= value) { + value = c->value + 1; } } } - 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); + 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; - 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; - } - } - } + 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; - return false; + cptr = ecs_get_mut_id(world, e, type); + cptr[0] = value; + + return 0; } -/* 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) +int flecs_add_constant_to_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - 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; - } + 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)); } } - return false; -} + /* 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; + } -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; - } + 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; } } - return false; + 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_query_init_query_monitors( - ecs_query_t *query) -{ - ecs_query_table_match_t *cur = query->list.first; +void flecs_set_primitive(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); - /* 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); + 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; + } } } -/* 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_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_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 - }; +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; } - query->flags |= EcsQueryHasRefs; + 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 -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; +void flecs_add_enum(ecs_iter_t *it) { + ecs_world_t *world = it->world; - 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); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; - /* Insert match to iteration list if table is not empty */ - if (!table || ecs_table_count(table) != 0) { - flecs_query_insert_table_node(query, qm); - } + if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { + continue; + } - return qm; + ecs_add_id(world, e, EcsExclusive); + ecs_add_id(world, e, EcsOneOf); + ecs_add_id(world, e, EcsTag); + } } 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); +void flecs_add_bitmask(ecs_iter_t *it) { + ecs_world_t *world = it->world; - 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; - } + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; - int32_t column = qm->columns[i]; - if (column > 0) { - qm->storage_columns[i] = ecs_table_type_to_storage_index(table, - qm->columns[i] - 1); - } else { - /* Shared field (not from table) */ - qm->storage_columns[i] = -2; - } + if (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { + continue; } + } +} - flecs_entity_filter_init(world, &qm->entity_filter, filter, - table, qm->ids, qm->columns); +static +void flecs_add_constant(ecs_iter_t *it) { + ecs_world_t *world = it->world; - if (qm->entity_filter) { - query->flags &= ~EcsQueryTrivialIter; + 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 (table->flags & EcsTableHasUnion) { - query->flags &= ~EcsQueryTrivialIter; + + 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); } } +} - /* 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 */ +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; } - 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 (!elem_count) { + ecs_err("array '%s' has size 0", ecs_get_name(world, e)); + continue; } - 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); - } - } + 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 -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; +void flecs_set_vector(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsVector *array = ecs_field(it, EcsVector, 1); - 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); + 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; - 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); + if (!elem_type) { + ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); + continue; } - ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); - flecs_query_set_table_match(world, query, qm, table, &it); + if (init_type_t(world, e, EcsVectorType, ecs_vec_t)) { + continue; + } } } 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; - } +void flecs_set_custom_type(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsOpaque *serialize = ecs_field(it, EcsOpaque, 1); - 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; - } + 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; - 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); + if (!elem_type) { + ecs_err("custom type '%s' has no mapping type", ecs_get_name(world, e)); + continue; + } - 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); + 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; } - ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); - flecs_query_set_table_match(world, query, qm, table, &it); + if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { + continue; + } } - - return qt != NULL; } -ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static) - -static -void flecs_query_sort_table( +bool flecs_unit_validate( 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_entity_t t, + EcsUnit *data) { - ecs_data_t *data = &table->data; - if (!ecs_vec_count(&data->entities)) { - /* Nothing to sort */ - return; + 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; + } } - int32_t count = flecs_table_data_count(data); - if (count < 2) { - return; + 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; + } } - ecs_entity_t *entities = ecs_vec_first(&data->entities); + 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; + } - void *ptr = NULL; - int32_t size = 0; - if (column_index != -1) { - ecs_type_info_t *ti = table->type_info[column_index]; - ecs_vec_t *column = &data->columns[column_index]; - size = ti->size; - ptr = ecs_vec_first(column); + 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 (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); - } -} + 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; + } + } -/* 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; + 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); + } -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); + 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 -ecs_entity_t e_from_helper( - sort_helper_t *helper) -{ - if (helper->row < helper->count) { - return helper->entities[helper->row]; - } else { - return 0; +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_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"); +void flecs_unit_quantity_monitor(ecs_iter_t *it) { + ecs_world_t *world = it->world; - 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; + 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); + } } +} - 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->storage_map[column - 1]; - ecs_vec_t *vec = &data->columns[column]; - 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; +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 + }); } - 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 ++; +static +void flecs_member_on_set(ecs_iter_t *it) { + EcsMember *mbr = it->ptrs[0]; + if (!mbr->count) { + mbr->count = 1; } +} - ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); +void FlecsMetaImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsMeta); - bool proceed; - do { - int32_t j, min = 0; - proceed = true; + ecs_set_name_prefix(world, "Ecs"); - ecs_entity_t e1; - while (!(e1 = e_from_helper(&helper[min]))) { - min ++; - if (min == to_sort) { - proceed = false; - break; - } - } + 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); - if (!proceed) { - break; - } + flecs_bootstrap_tag(world, EcsConstant); + flecs_bootstrap_tag(world, EcsQuantity); - for (j = min + 1; j < to_sort; j++) { - ecs_entity_t e2 = e_from_helper(&helper[j]); - if (!e2) { - continue; - } + ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); - const void *ptr1 = ptr_from_helper(&helper[min]); - const void *ptr2 = ptr_from_helper(&helper[j]); + ecs_set_hooks(world, EcsMetaTypeSerialized, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsMetaTypeSerialized), + .copy = ecs_copy(EcsMetaTypeSerialized), + .dtor = ecs_dtor(EcsMetaTypeSerialized) + }); - if (compare(e1, ptr1, e2, ptr2) > 0) { - min = j; - e1 = e_from_helper(&helper[min]); - } - } + ecs_set_hooks(world, EcsStruct, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsStruct), + .copy = ecs_copy(EcsStruct), + .dtor = ecs_dtor(EcsStruct) + }); - 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 ++; - } + ecs_set_hooks(world, EcsMember, { + .ctor = ecs_default_ctor, + .on_set = flecs_member_on_set + }); - cur_helper->row ++; - } while (proceed); + ecs_set_hooks(world, EcsMemberRanges, { + .ctor = ecs_default_ctor + }); - /* 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]; - } + ecs_set_hooks(world, EcsEnum, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsEnum), + .copy = ecs_copy(EcsEnum), + .dtor = ecs_dtor(EcsEnum) + }); - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; + ecs_set_hooks(world, EcsBitmask, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsBitmask), + .copy = ecs_copy(EcsBitmask), + .dtor = ecs_dtor(EcsBitmask) + }); - flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); -} + ecs_set_hooks(world, EcsUnit, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsUnit), + .copy = ecs_copy(EcsUnit), + .dtor = ecs_dtor(EcsUnit) + }); -static -void flecs_query_build_sorted_tables( - ecs_query_t *query) -{ - ecs_vec_clear(&query->table_slices); + ecs_set_hooks(world, EcsUnitPrefix, { + .ctor = ecs_default_ctor, + .move = ecs_move(EcsUnitPrefix), + .copy = ecs_copy(EcsUnitPrefix), + .dtor = ecs_dtor(EcsUnitPrefix) + }); - 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); + /* 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 + }); - /* Sort tables in current group */ - flecs_query_build_sorted_table_range(query, list); + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_member + }); - /* Find next group to sort */ - cur = list->last->next; - } while (cur); - } - } else { - flecs_query_build_sorted_table_range(query, &query->list); - } -} + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMemberRanges), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_member_ranges + }); -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_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_enum + }); - 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; + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_bitmask + }); - /* Iterate over non-empty tables. Don't bother with empty tables as they - * have nothing to sort */ + ecs_observer(world, { + .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_constant + }); - bool tables_sorted = false; + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_add_constant + }); - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; - flecs_table_cache_iter(&query->cache, &it); + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_array + }); - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - ecs_table_t *table = qt->hdr.table; - bool dirty = false; + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_vector + }); - if (flecs_query_check_table_monitor(query, qt, 0)) { - dirty = true; - } + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_custom_type + }); - int32_t column = -1; - if (order_by_component) { - if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) { - dirty = true; - } + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_unit + }); - if (dirty) { - column = -1; + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_serialized_init + }); - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - column = ecs_search(world, storage_table, - order_by_component, 0); - } + ecs_observer(world, { + .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_init_default_ctor + }); - if (column == -1) { - /* Component is shared, no sorting is needed */ - dirty = false; - } - } - } + ecs_observer(world, { + .filter.terms = { + { .id = ecs_id(EcsUnit) }, + { .id = EcsQuantity } + }, + .events = { EcsMonitor }, + .callback = flecs_unit_quantity_monitor + }); + ecs_set_scope(world, old_scope); - if (!dirty) { - continue; - } + /* 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\ + }); - /* 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; - } + 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); - if (tables_sorted || query->match_count != query->prev_match_count) { - flecs_query_build_sorted_tables(query); - query->match_count ++; /* Increase version if tables changed */ - } -} + #undef ECS_PRIMITIVE -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; - } - } + 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) + }); - return false; -} + /* Set default child components */ + ecs_add_pair(world, ecs_id(EcsStruct), + EcsDefaultChildComponent, ecs_id(EcsMember)); -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; + ecs_add_pair(world, ecs_id(EcsMember), + EcsDefaultChildComponent, ecs_id(EcsMember)); - for (i = 0; i < count; i++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *src = &term->src; + ecs_add_pair(world, ecs_id(EcsEnum), + EcsDefaultChildComponent, EcsConstant); - 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); + ecs_add_pair(world, ecs_id(EcsBitmask), + EcsDefaultChildComponent, EcsConstant); - } else if (src->flags & EcsSelf && !ecs_term_match_this(term)) { - callback(world, term->id, query); + /* 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" } } - } -} + }); -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; -} + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMetaType), + .members = { + { .name = "kind", .type = type_kind } + } + }); -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; + 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" } + } + }); - 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; + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsPrimitive), + .members = { + { .name = "kind", .type = primitive_kind } + } + }); - 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); + 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) } + } + }); - (void)first; - (void)second; - (void)is_src_ok; - (void)is_first_ok; - (void)is_second_ok; + 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) } + } + }); - /* 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"); + 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 } + } + }); - if (inout != EcsIn && inout != EcsInOutNone) { - query->flags |= EcsQueryHasOutColumns; + 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) }, } + }); - 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; + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsVector), + .members = { + { .name = "type", .type = ecs_id(ecs_entity_t) } } - } + }); - query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsOpaque), + .members = { + { .name = "as_type", .type = ecs_id(ecs_entity_t) } + } + }); - if (!(query->flags & EcsQueryIsSubquery)) { - flecs_query_for_each_component_monitor(world, query, flecs_monitor_register); - } + 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 - return 0; -error: - return -1; +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; } -/** 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; +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; +} - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; +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; +} - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); +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; +} - 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); - } - } +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_assert(cur_count || query->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); + 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 -void flecs_query_add_subquery( - ecs_world_t *world, - ecs_query_t *parent, - ecs_query_t *subquery) +int flecs_meta_serialize_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) { - 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); - } + (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 -void flecs_query_notify_subqueries( +int flecs_meta_serialize_bitmask( ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) { - 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); - } - } + (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; } -/* Remove table */ static -void flecs_query_table_match_free( - ecs_query_t *query, - ecs_query_table_t *elem, - ecs_query_table_match_t *first) +int flecs_meta_serialize_array( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) { - ecs_query_table_match_t *cur, *next; - ecs_world_t *world = query->filter.world; + (void)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); + 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; +} - 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); +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 */ + } - next = cur->next_match; + flecs_meta_serialize_type(world, ptr->type, 0, ops); - flecs_bfree(&world->allocators.query_table_match, cur); - } + ecs_meta_type_op_t *first = ecs_vec_first(ops); + first->count = ptr->count; + return 0; } static -void flecs_query_table_free( - ecs_query_t *query, - ecs_query_table_t *elem) +int flecs_meta_serialize_vector( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) { - flecs_query_table_match_free(query, elem, elem->first); - flecs_bfree(&query->filter.world->allocators.query_table, elem); + (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 -void flecs_query_unmatch_table( - ecs_query_t *query, - ecs_table_t *table, - ecs_query_table_t *elem) +int flecs_meta_serialize_custom_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) { - 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); - } + (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; } -/* Rematch system with tables after a change happened to a watched entity */ static -void flecs_query_rematch_tables( +int flecs_meta_serialize_struct( ecs_world_t *world, - ecs_query_t *query, - ecs_query_t *parent_query) + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) { - 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); - } + const EcsStruct *ptr = ecs_get(world, type, EcsStruct); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterNoData); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); + 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); - world->info.rematch_count_total ++; - int32_t rematch_count = ++ query->rematch_count; + ecs_member_t *members = ecs_vec_first(&ptr->members); + int32_t i, count = ecs_vec_count(&ptr->members); - ecs_time_t t = {0}; - if (world->flags & EcsWorldMeasureFrameTime) { - ecs_time_measure(&t); + ecs_hashmap_t *member_index = NULL; + if (count) { + op->members = member_index = flecs_name_index_new( + world, &world->allocator); } - 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; + for (i = 0; i < count; i ++) { + ecs_member_t *member = &members[i]; - qt = ecs_table_cache_get(&query->cache, table); - if (!qt) { - qt = flecs_query_table_insert(world, query, table); - } + cur = ecs_vec_count(ops); + flecs_meta_serialize_type(world, + member->type, offset + member->offset, ops); - 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); + op = flecs_meta_ops_get(ops, cur); + if (!op->type) { + op->type = member->type; } - 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 (op->count <= 1) { + op->count = member->count; } - } - if (qm && qm->next_match) { - flecs_query_table_match_free(query, qt, qm->next_match); - qm->next_match = NULL; - } + const char *member_name = member->name; + op->name = member_name; + op->op_count = ecs_vec_count(ops) - cur; + op->member_index = i; - /* 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); - } - } + flecs_name_index_ensure( + member_index, flecs_ito(uint64_t, cur - first - 1), + member_name, 0, 0); } - if (world->flags & EcsWorldMeasureFrameTime) { - world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); - } + flecs_meta_ops_add(ops, EcsOpPop); + flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first; + return 0; } static -void flecs_query_remove_subquery( - ecs_query_t *parent, - ecs_query_t *sub) +int flecs_meta_serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) { - 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); + 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; + } - for (i = 0; i < count; i ++) { - if (sq[i] == sub) { - break; - } + 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); } - ecs_vec_remove_t(&parent->subqueries, ecs_query_t*, i); + return 0; } -/* -- Private API -- */ - -void flecs_query_notify( +static +int flecs_meta_serialize_component( ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) + ecs_entity_t type, + ecs_vec_t *ops) { - 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; + 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 (notify) { - flecs_query_notify_subqueries(world, query, event); + if (ptr->kind == EcsArrayType) { + return flecs_meta_serialize_array_component(world, type, ops); + } else { + return flecs_meta_serialize_type(world, type, 0, ops); } } -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) +void ecs_meta_type_serialized_init( + ecs_iter_t *it) { - 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); + ecs_world_t *world = it->world; - /* 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; + 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); - 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; - } + EcsMetaTypeSerialized *ptr = ecs_get_mut( + world, e, EcsMetaTypeSerialized); + if (ptr->ops.array) { + ecs_meta_dtor_serialized(ptr); } - ecs_check(order_by_term != -1, ECS_INVALID_PARAMETER, - "sorted component not is queried for"); + ptr->ops = ops; } +} - query->order_by_component = order_by_component; - query->order_by = order_by; - query->order_by_term = order_by_term; - query->sort_table = action; +#endif - ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); - flecs_query_sort_tables(world, query); +/** + * @file addons/os_api_impl/os_api_impl.c + * @brief Builtin implementation for OS API. + */ - if (!query->table_slices.array) { - flecs_query_build_sorted_tables(query); + +#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(); +} - query->flags &= ~EcsQueryTrivialIter; -error: - return; +static +int32_t win_ainc( + int32_t *count) +{ + return InterlockedIncrement((volatile long*)count); } 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); +int32_t win_adec( + int32_t *count) +{ + return InterlockedDecrement((volatile long*)count); +} - if (!group_by) { - /* Builtin function that groups by relationship */ - group_by = flecs_query_default_group_by; - } +static +int64_t win_lainc( + int64_t *count) +{ + return InterlockedIncrement64(count); +} - query->group_by_id = sort_component; - query->group_by = group_by; +static +int64_t win_ladec( + int64_t *count) +{ + return InterlockedDecrement64(count); +} - ecs_map_init_w_params(&query->groups, - &query->filter.world->allocators.query_table_list); -error: - return; +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; } -/* 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) +void win_mutex_free( + ecs_os_mutex_t m) { - ecs_poly_assert(poly, ecs_query_t); + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + DeleteCriticalSection(mutex); + ecs_os_free(mutex); +} - if (filter) { - iter[1] = ecs_query_iter(world, (ecs_query_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); - } else { - iter[0] = ecs_query_iter(world, (ecs_query_t*)poly); - } +static +void win_mutex_lock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + EnterCriticalSection(mutex); } static -void flecs_query_on_event( - ecs_iter_t *it) +void win_mutex_unlock( + ecs_os_mutex_t m) { - /* 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; - } + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + LeaveCriticalSection(mutex); +} - ecs_query_t *query = o->ctx; - ecs_table_t *table = it->table; - ecs_entity_t event = it->event; +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; +} - 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; - } +static +void win_cond_free( + ecs_os_cond_t c) +{ + (void)c; +} - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); +static +void win_cond_signal( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeConditionVariable(cond); +} - /* 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; - } +static +void win_cond_broadcast( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeAllConditionVariable(cond); +} - 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); - } +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 flecs_query_table_cache_free( - ecs_query_t *query) +void win_sleep( + int32_t sec, + int32_t nanosec) { - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; + HANDLE timer; + LARGE_INTEGER ft; - 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); - } - } + ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); - ecs_table_cache_fini(&query->cache); + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); } static -void flecs_query_allocators_init( - ecs_query_t *query) +void win_enable_high_timer_resolution(bool enable) { - 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)); + HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); + if (!hntdll) { + return; } -} -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); + union { + LONG (__stdcall *f)( + ULONG desired, BOOLEAN set, ULONG * current); + FARPROC p; + } func; + + func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); + if(!func.p) { + return; } -} -static -void flecs_query_fini( - ecs_query_t *query) -{ - ecs_world_t *world = query->filter.world; + ULONG current, resolution = 10000; /* 1 ms */ - 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 (!enable && win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); + win_current_resolution = 0; + return; + } else if (!enable) { + return; } - if (query->group_by_ctx_free) { - if (query->group_by_ctx) { - query->group_by_ctx_free(query->group_by_ctx); - } + if (resolution == win_current_resolution) { + return; } - if ((query->flags & EcsQueryIsSubquery) && - !(query->flags & EcsQueryIsOrphaned)) - { - flecs_query_remove_subquery(query->parent, query); + if (win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); } - flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){ - .kind = EcsQueryOrphan - }); + if (func.f(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(func.f(resolution, 1, ¤t)) return; + } - flecs_query_for_each_component_monitor(world, query, - flecs_monitor_unregister); - flecs_query_table_cache_free(query); + win_current_resolution = resolution; +} - ecs_map_fini(&query->groups); +static +uint64_t win_time_now(void) { + uint64_t now; - 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); + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); - flecs_query_allocators_fini(query); + return now; +} - ecs_poly_free(query, ecs_query_t); +static +void win_fini(void) { + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(false); + } } -/* -- Public API -- */ +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); -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_os_api_t api = ecs_os_api; - 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; + 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; - observer_desc.filter.flags = EcsFilterMatchEmptyTables; - observer_desc.filter.storage = &result->filter; - result->filter = ECS_FILTER_INIT; + win_time_setup(); - if (ecs_filter_init(world, &observer_desc.filter) == NULL) { - goto error; + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(true); } - ECS_BIT_COND(result->flags, EcsQueryTrivialIter, - !!(result->filter.flags & EcsFilterMatchOnlyThis)); + ecs_os_set_api(&api); +} - flecs_query_allocators_init(result); +#else +/** + * @file addons/os_api_impl/posix_impl.inl + * @brief Builtin POSIX implementation for OS API. + */ - 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; +#include "pthread.h" - /* 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 */ +#if defined(__APPLE__) && defined(__MACH__) +#include +#elif defined(__EMSCRIPTEN__) +#include +#else +#include +#endif - entity = ecs_observer_init(world, &observer_desc); - if (!entity) { - goto error; - } - } +/* 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 - result->iterable.init = flecs_query_iter_init; - result->dtor = (ecs_poly_dtor_t)flecs_query_fini; - result->prev_match_count = -1; +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 (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); + if (pthread_create (thread, NULL, callback, arg) != 0) { + ecs_os_abort(); } - ecs_log_push_1(); - - if (flecs_query_process_signature(world, result)) { - goto error; - } + return (ecs_os_thread_t)(uintptr_t)thread; +} - /* 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]; - } +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; +} - 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; - } +static +ecs_os_thread_id_t posix_thread_self(void) +{ + return (ecs_os_thread_id_t)pthread_self(); +} - if (desc->parent != NULL) { - result->flags |= EcsQueryIsSubquery; +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(); } - - /* 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); - } - } + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); } + return value; +#endif +} - if (!entity) { - entity = ecs_new_id(world); +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(); } - - 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); + value = (*count) -= 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); } - 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); + return value; +#endif +} - if (!desc->parent) { - flecs_query_match_tables(world, result); - } else { - flecs_query_add_subquery(world, desc->parent, result); - result->parent = desc->parent; +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(); } - - if (desc->order_by) { - flecs_query_order_by( - world, result, desc->order_by_component, desc->order_by, - desc->sort_table); + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); } + return value; +#endif +} - if (!ecs_query_table_count(result) && result->filter.term_count) { - ecs_add_id(world, entity, EcsEmpty); +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 +} - 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); +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 NULL; + return (ecs_os_mutex_t)(uintptr_t)mutex; } -void ecs_query_fini( - ecs_query_t *query) +static +void posix_mutex_free( + ecs_os_mutex_t m) { - ecs_poly_assert(query, ecs_query_t); - ecs_delete(query->filter.world, query->filter.entity); + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + pthread_mutex_destroy(mutex); + ecs_os_free(mutex); } -const ecs_filter_t* ecs_query_get_filter( - const ecs_query_t *query) +static +void posix_mutex_lock( + ecs_os_mutex_t m) { - ecs_poly_assert(query, ecs_query_t); - return &query->filter; + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_lock(mutex)) { + abort(); + } } -ecs_iter_t ecs_query_iter( - const ecs_world_t *stage, - ecs_query_t *query) +static +void posix_mutex_unlock( + ecs_os_mutex_t m) { - 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); + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_unlock(mutex)) { + abort(); } +} - /* 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); +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; +} - 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); +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); +} - ecs_iter_t result = { - .real_world = world, - .world = (ecs_world_t*)stage, - .terms = query->filter.terms, - .field_count = query->filter.field_count, - .table_count = table_count, - .priv.iter.query = it, - .next = ecs_query_next, - }; - - 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_world_t*)stage, &query->filter, EcsIterIgnoreThis); - if (!ecs_filter_next(&fit)) { - /* No match, so return nothing */ - ecs_iter_fini(&fit); - 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); +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(); } - - 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) +static +void posix_cond_broadcast( + ecs_os_cond_t c) { - 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; + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_broadcast(cond)) { + abort(); } +} - 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; +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(); } - -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; +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; } - return &node->info; + 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 } -void* ecs_query_get_group_ctx( - const ecs_query_t *query, - uint64_t group_id) +static +void posix_sleep( + int32_t sec, + int32_t nanosec) { - const ecs_query_group_info_t *info = - ecs_query_get_group_info(query, group_id); - if (!info) { - return NULL; - } else { - return info->ctx; + 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 -void flecs_query_mark_columns_dirty( - ecs_query_t *query, - ecs_query_table_match_t *qm) -{ - ecs_table_t *table = qm->table; - if (!table) { - return; - } +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 - int32_t *dirty_state = table->dirty_state; - if (dirty_state) { - int32_t *storage_columns = qm->storage_columns; - ecs_filter_t *filter = &query->filter; - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; +static +uint64_t posix_time_now(void) { + ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->inout == EcsIn || term->inout == EcsInOutNone) { - /* Don't mark readonly terms dirty */ - continue; - } + uint64_t now; - int32_t field = term->field_index; - int32_t column = storage_columns[field]; - if (column < 0) { - continue; - } + #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 - dirty_state[column + 1] ++; - } - } + return now; } -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); +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); - ecs_query_iter_t *iter = &it->priv.iter.query; - ecs_query_table_match_t *node = iter->node; - ecs_query_t *query = iter->query; + ecs_os_api_t api = ecs_os_api; - ecs_query_table_match_t *prev = iter->prev; - if (prev) { - if (query->flags & EcsQueryHasMonitor) { - flecs_query_sync_match_monitor(query, prev); - } - if (query->flags & EcsQueryHasOutColumns) { - if (it->count) { - flecs_query_mark_columns_dirty(query, prev); - } - } - } + 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; - 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; - } + posix_time_setup(); -error: - query->match_count = query->prev_match_count; - ecs_iter_fini(it); - return false; + ecs_os_set_api(&api); } -static -void flecs_query_populate_trivial( - ecs_iter_t *it, - ecs_query_table_match_t *match) -{; - ecs_table_t *table = match->table; - int32_t count = ecs_table_count(table); - - it->ids = match->ids; - it->sources = match->sources; - it->columns = match->columns; - it->group_id = match->group_id; - it->instance_count = 0; - it->offset = 0; - it->count = count; - it->references = ecs_vec_first(&match->refs); +#endif +#endif - 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; - } +/** + * @file addons/ipeline/pipeline.c + * @brief Functions for building and running pipelines. + */ - ecs_size_t size = it->sizes[i]; - if (!size) { - it->ptrs[i] = NULL; - continue; - } - it->ptrs[i] = ecs_vec_get(&data->columns[column], - it->sizes[i], 0); - } - } +#ifdef FLECS_PIPELINE - it->frame_offset += it->table ? ecs_table_count(it->table) : 0; - it->table = table; - it->entities = ecs_vec_first(&data->entities); - } else { - flecs_iter_populate_data(it->real_world, it, table, 0, count, it->ptrs); +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); } } -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; - } +static ECS_MOVE(EcsPipeline, dst, src, { + flecs_pipeline_free(dst->state); + dst->state = src->state; + src->state = NULL; +}) - 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; +static ECS_DTOR(EcsPipeline, ptr, { + flecs_pipeline_free(ptr->state); +}) -repeat: - result = EcsIterNextYield; +typedef enum ecs_write_kind_t { + WriteStateNone = 0, + WriteStateToStage, +} ecs_write_kind_t; - ecs_os_memcpy_n(it->sources, match->sources, ecs_entity_t, - filter->field_count); +typedef struct ecs_write_state_t { + bool write_barrier; + ecs_map_t ids; + ecs_map_t wildcard_ids; +} ecs_write_state_t; - 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; - } +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; - it->ids[field] = match->ids[field]; - it->columns[field] = match->columns[field]; + if (write_state->write_barrier) { + /* Any component could have been written */ + return WriteStateToStage; } - 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; - } + 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; } + } - it->group_id = match->group_id; + if (!ecs_id_is_wildcard(id)) { + if (ecs_map_get(&write_state->ids, id)) { + result = WriteStateToStage; + } } else { - range->offset = 0; - range->count = 0; + 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 (when_changed) { - if (!ecs_query_changed(NULL, it)) { - if (result == EcsIterYield) { - goto repeat; - } else { - result = EcsIterNext; - goto done; + 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; } } } - 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) +static +void flecs_pipeline_set_write_state( + ecs_write_state_t *write_state, + ecs_id_t id) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + if (id == EcsWildcard) { + /* If writing to wildcard, flag all components as written */ + write_state->write_barrier = true; + return; + } - if (flecs_iter_next_row(it)) { - return true; + ecs_map_t *ids; + if (ecs_id_is_wildcard(id)) { + ids = &write_state->wildcard_ids; + } else { + ids = &write_state->ids; } - return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); -error: - return false; + ecs_map_ensure(ids, id)[0] = true; } -bool ecs_query_next_instanced( - ecs_iter_t *it) +static +void flecs_pipeline_reset_write_state( + ecs_write_state_t *write_state) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_map_clear(&write_state->ids); + ecs_map_clear(&write_state->wildcard_ids); + write_state->write_barrier = false; +} - ecs_query_iter_t *iter = &it->priv.iter.query; - ecs_query_t *query = iter->query; - ecs_flags32_t flags = query->flags; +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_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 & EcsQueryHasOutColumns) { - flecs_query_mark_columns_dirty(query, prev); - } + ecs_term_id_t *src = &term->src; + if (src->flags & EcsInOutNone) { + return false; } - flecs_iter_validate(it); - iter->skip_count = 0; + 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)); - /* 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); + 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; } - /* 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); + 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; } } -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 (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 (!query) { - query = it->priv.iter.query.query; - } else { - ecs_check(query == it->priv.iter.query.query, - ECS_INVALID_PARAMETER, NULL); + 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; } - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_poly_assert(query, ecs_query_t); - - return flecs_query_check_match_monitor(query, qm, it); + 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; + } } - ecs_poly_assert(query, ecs_query_t); - ecs_check(!(query->flags & EcsQueryIsOrphaned), - ECS_INVALID_PARAMETER, NULL); + return false; +} - flecs_process_pending_tables(query->filter.world); +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; - if (!(query->flags & EcsQueryHasMonitor)) { - query->flags |= EcsQueryHasMonitor; - flecs_query_init_query_monitors(query); - return true; /* Monitors didn't exist yet */ + /* 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); + } } - if (query->match_count != query->prev_match_count) { - return true; + /* 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 flecs_query_check_query_monitor(query); -error: - return false; + return needs_merge; } -void ecs_query_skip( +static +EcsPoly* flecs_pipeline_term_system( 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; - } + 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; } -bool ecs_query_orphaned( - const ecs_query_t *query) +static +bool flecs_pipeline_build( + ecs_world_t *world, + ecs_pipeline_state_t *pq) { - ecs_poly_assert(query, ecs_query_t); - return query->flags & EcsQueryIsOrphaned; -} + ecs_iter_t it = ecs_query_iter(world, pq->query); -char* ecs_query_str( - const ecs_query_t *query) -{ - return ecs_filter_str(query->filter.world, &query->filter); -} + if (pq->match_count == pq->query->match_count) { + /* No need to rebuild the pipeline */ + ecs_iter_fini(&it); + return false; + } -int32_t ecs_query_table_count( - const ecs_query_t *query) -{ - ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); - return query->cache.tables.count; -} + world->info.pipeline_build_count_total ++; + pq->rebuild_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; -} + 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); -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; + 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_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); + 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 ++; + } + } } - for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) { - result += ecs_table_count(cur->table); + if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { + ecs_vec_remove_last(&pq->ops); } - return result; -} + ecs_map_fini(&ws.ids); + ecs_map_fini(&ws.wildcard_ids); -/** - * @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]. - */ + 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(); -/* Id sequence (type) utilities */ + ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", + op->multi_threaded, !op->no_readonly); + ecs_log_push_1(); -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)); -} + 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_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_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; +#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 - int32_t count_1 = type_1->count; - int32_t count_2 = type_2->count; + ecs_assert(op[op_index].offset + ran_since_merge == i, + ECS_INTERNAL_ERROR, NULL); - if (count_1 != count_2) { - return (count_1 > count_2) - (count_1 < count_2); - } + 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(); + } - 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); + 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(); } - return result; -} + 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); -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); + return true; } -/* 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) +void flecs_pipeline_next_system( + ecs_pipeline_state_t *pq) { - ecs_id_t *array = type->array; - int32_t i, count = type->count; + if (!pq->cur_op) { + return; + } - for (i = offset; i < count; i ++) { - ecs_id_t id = array[i]; - if (id == to_add) { - return -1; - } - if (id > to_add) { - return i; + 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; } - } - return i; + } } -/* Find location of id in type */ -static -int flecs_type_find( - const ecs_type_t *type, - ecs_id_t id) +bool flecs_pipeline_update( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + bool start_of_frame) { - ecs_id_t *array = type->array; - int32_t i, count = type->count; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); - 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; - } + /* 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); } - 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; + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); - for (; i < count; i ++) { - ecs_id_t cur = array[i]; - if (!ecs_id_match(cur, wildcard)) { - break; + 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 i - offset; + return rebuilt; } -/* Create type from source type with id */ -static -int flecs_type_new_with( +void ecs_run_pipeline( ecs_world_t *world, - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t with) + ecs_entity_t pipeline, + ecs_ftime_t delta_time) { - 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); + if (!pipeline) { + pipeline = world->pipeline; } - int32_t remain = src->count - at; - if (remain) { - ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); + /* create any worker task threads request */ + if (ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); } - dst_array[at] = with; + EcsPipeline *p = + ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); + flecs_workers_progress(world, p->state, delta_time); - return 0; + if (ecs_using_task_threads(world)) { + /* task threads were temporary and may now be joined */ + flecs_join_worker_threads(world); + } } -/* 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) +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) { - *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); - } + ecs_pipeline_state_t* pq = world->pq; + ecs_pipeline_op_t* op = pq->cur_op; + int32_t i = pq->cur_i; - 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 ++; + 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_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_system_t* sys = ecs_poly(poly->poly, ecs_system_t); + + /* 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; } - } - dst->count = w; - if (w != count) { - dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); + 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 0; + return i; } -/* Create type from source type without id */ -static -int flecs_type_new_without( +void flecs_run_pipeline( ecs_world_t *world, - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t without) + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time) { - ecs_id_t *src_array = src->array; - int32_t count = 1, at = flecs_type_find(src, without); - if (at == -1) { - return -1; - } + 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); - int32_t src_count = src->count; - if (src_count == 1) { - dst->array = NULL; - dst->count = 0; - return 0; - } + 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); - 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); - } + ecs_assert(!stage_index, ECS_INVALID_OPERATION, NULL); - int32_t dst_count = src_count - count; - dst->count = dst_count; - if (!dst_count) { - dst->array = NULL; - return 0; - } + bool multi_threaded = ecs_get_stage_count(world) > 1; - ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); - dst->array = dst_array; + // Update the pipeline the workers will execute + world->pq = pq; - if (at) { - ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); - } + // Update the pipeline before waking the workers. + flecs_pipeline_update(world, pq, true); - int32_t remain = dst_count - at; - if (remain) { - ecs_os_memcpy_n( - &dst_array[at], &src_array[at + count], ecs_id_t, remain); - } + // 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; + } - return 0; -} + bool no_readonly = pq->cur_op->no_readonly; + bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; -/* 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 }; - } + pq->no_readonly = no_readonly; - 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 - }; -} + if (!no_readonly) { + ecs_readonly_begin(world); + } -/* 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); - } -} + ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); + ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); -/* 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; - } -} + if (op_multi_threaded) { + flecs_signal_workers(world); + } -/* Graph edge utilities */ + ecs_time_t st = { 0 }; + bool measure_time = world->flags & EcsWorldMeasureSystemTime; + if (measure_time) { + ecs_time_measure(&st); + } -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); -} + const int32_t i = flecs_run_pipeline_ops( + world, stage, stage_index, stage_count, delta_time); -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); -} + if (measure_time) { + /* Don't include merge time in system time */ + world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); + } -void flecs_table_diff_builder_clear( - ecs_table_diff_builder_t *builder) -{ - ecs_vec_clear(&builder->added); - ecs_vec_clear(&builder->removed); -} + if (op_multi_threaded) { + flecs_wait_for_sync(world); + } -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); - } -} + if (!no_readonly) { + ecs_time_t mt = { 0 }; + if (measure_time) { + ecs_time_measure(&mt); + } -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); -} + 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->commands); + } -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 }; + 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_table_diff_build_add_type_to_vec( - ecs_world_t *world, - ecs_vec_t *vec, - ecs_type_t *add) +void flecs_run_startup_systems( + ecs_world_t *world) { - if (!add || !add->count) { + 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; } - 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); -} + 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 */ -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); -} + /* 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(); -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); - } + /* 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_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; - } + ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); + ecs_log_push_2(); + ecs_delete(world, start_pip); + ecs_log_pop_2(); - if (id < FLECS_HI_COMPONENT_ID) { - edge = &edges->lo[id]; - } else { - edge = flecs_bcalloc(&world->allocators.graph_edge); - } + world->stage_count = stage_count; + ecs_log_pop_2(); - r[0] = edge; - return edge; +error: + return; } -static -ecs_graph_edge_t* flecs_table_ensure_edge( +bool ecs_progress( ecs_world_t *world, - ecs_graph_edges_t *edges, - ecs_id_t id) + ecs_ftime_t user_delta_time) { - ecs_graph_edge_t *edge; + ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); - 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); + /* If this is the first frame, run startup systems */ + if (world->info.frame_count_total == 0) { + flecs_run_startup_systems(world); } - 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; + /* create any worker task threads request */ + if (ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); } - /* Remove data associated with edge */ - ecs_table_diff_t *diff = edge->diff; - if (diff) { - flecs_table_diff_free(world, diff); - } + 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(); - /* 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); + ecs_frame_end(world); + + if (ecs_using_task_threads(world)) { + /* task threads were temporary and may now be joined */ + flecs_join_worker_threads(world); } -} -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); + return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +error: + return false; } -static -void flecs_table_init_edges( - ecs_graph_edges_t *edges) +void ecs_set_time_scale( + ecs_world_t *world, + ecs_ftime_t scale) { - edges->lo = NULL; - edges->hi = NULL; + world->info.time_scale = scale; } -static -void flecs_table_init_node( - ecs_graph_node_t *node) +void ecs_reset_clock( + ecs_world_t *world) { - flecs_table_init_edges(&node->add); - flecs_table_init_edges(&node->remove); + world->info.world_time_total = 0; + world->info.world_time_total_raw = 0; } -bool flecs_table_records_update_empty( - ecs_table_t *table) +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline) { - 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); - } + ecs_poly_assert(world, ecs_world_t); + ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, + ECS_INVALID_PARAMETER, "not a pipeline"); - return result; + world->pipeline = pipeline; +error: + return; } -static -void flecs_init_table( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *prev) +ecs_entity_t ecs_get_pipeline( + const ecs_world_t *world) { - table->type_info = NULL; - table->flags = 0; - table->dirty_state = NULL; - table->_->lock = 0; - table->_->refcount = 1; - table->_->generation = 0; - - flecs_table_init_node(&table->node); - - flecs_table_init(world, table, prev); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->pipeline; +error: + return 0; } -static -ecs_table_t *flecs_create_table( +ecs_entity_t ecs_pipeline_init( ecs_world_t *world, - ecs_type_t *type, - flecs_hashmap_result_t table_elem, - ecs_table_t *prev) + const ecs_pipeline_desc_t *desc) { - 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; + ecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - 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_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world, 0); } - 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); + ecs_query_desc_t qd = desc->query; + if (!qd.order_by) { + qd.order_by = flecs_entity_compare; + } + qd.filter.entity = result; - /* Update counters */ - world->info.table_count ++; - world->info.table_record_count += result->_->record_count; - world->info.table_storage_count += result->storage_count; - world->info.empty_table_count ++; - world->info.table_create_total ++; - - if (!result->storage_count) { - world->info.tag_table_count ++; - } else { - world->info.trivial_table_count += !(result->flags & EcsTableIsComplex); + ecs_query_t *query = ecs_query_init(world, &qd); + if (!query) { + ecs_delete(world, result); + return 0; } - ecs_log_pop_2(); + 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 -ecs_table_t* flecs_table_ensure( +void FlecsPipelineFini( 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); + void *ctx) +{ + (void)ctx; + if (ecs_get_stage_count(world)) { + ecs_set_threads(world, 0); } - ecs_type_t copy = flecs_type_copy(world, type); - return flecs_create_table(world, ©, elem, prev); + 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_diff_insert_added( +void flecs_bootstrap_phase_( ecs_world_t *world, - ecs_table_diff_builder_t *diff, - ecs_id_t id) + ecs_entity_t phase, + ecs_entity_t depends_on) { - ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; + ecs_add_id(world, phase, EcsPhase); + if (depends_on) { + ecs_add_pair(world, phase, EcsDependsOn, depends_on); + } } -static -void flecs_diff_insert_removed( - ecs_world_t *world, - ecs_table_diff_builder_t *diff, - ecs_id_t id) +void FlecsPipelineImport( + ecs_world_t *world) { - ecs_allocator_t *a = &world->allocator; - ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; -} + ECS_MODULE(world, FlecsPipeline); + ECS_IMPORT(world, FlecsSystem); -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_set_name_prefix(world, "Ecs"); - ecs_type_t node_type = node->type; - ecs_type_t next_type = next->type; + flecs_bootstrap_component(world, EcsPipeline); + flecs_bootstrap_tag(world, EcsPhase); - 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); + /* 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); - /* 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]; + 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); - bool added = id_next < id_node; - bool removed = id_node < id_next; + ecs_set_hooks(world, EcsPipeline, { + .ctor = ecs_default_ctor, + .dtor = ecs_dtor(EcsPipeline), + .move = ecs_move(EcsPipeline) + }); - trivial_edge &= !added || id_next == id; - trivial_edge &= !removed || id_node == id; + 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 + } + }); - added_count += added; - removed_count += removed; + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); +} - i_node += id_node <= id_next; - i_next += id_next <= id_node; - } +#endif - added_count += next_count - i_next; - removed_count += node_count - i_node; +/** + * @file addons/pipeline/worker.c + * @brief Functions for running pipelines on one or more threads. + */ - 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 */ +#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; } - ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; - int32_t added_offset = builder->added.count; - int32_t removed_offset = builder->removed.count; + /* 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); + } - 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]; + /* 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); +} - 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); - } +/* Worker thread */ +static +void* flecs_worker(void *arg) { + ecs_stage_t *stage = arg; + ecs_world_t *world = stage->world; - i_node += id_node <= id_next; - i_next += id_next <= id_node; - } + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); - for (; i_next < next_count; i_next ++) { - flecs_diff_insert_added(world, builder, ids_next[i_next]); + 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); } - for (; i_node < node_count; i_node ++) { - flecs_diff_insert_removed(world, builder, ids_node[i_node]); + + 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_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_dbg_2("worker %d: finalizing", stage->id); - ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); + 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; } -static -void flecs_add_overrides_for_base( - ecs_world_t *world, - ecs_type_t *dst_type, - ecs_id_t pair) +/* Start threads */ +void flecs_create_worker_threads( + ecs_world_t *world) { - 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_poly_assert(world, ecs_world_t); + int32_t stages = ecs_get_stage_count(world); - 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]; - if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { - flecs_type_add(world, dst_type, 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) { - flecs_type_add(world, dst_type, id); - } - } - } - } + 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); - if (flags & EcsTableHasIsA) { - const 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->column, end = i + tr->count; - for (; i != end; i ++) { - flecs_add_overrides_for_base(world, dst_type, ids[i]); + 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_add_with_property( +void flecs_start_workers( ecs_world_t *world, - ecs_id_record_t *idr_with_wildcard, - ecs_type_t *dst_type, - ecs_entity_t r, - ecs_entity_t o) + int32_t threads) { - 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; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr_with_wildcard, table); - if (tr) { - int32_t i = tr->column, end = i + tr->count; - ecs_id_t *ids = table->type.array; + ecs_set_stage_count(world, threads); - 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); - } + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); - flecs_type_add(world, dst_type, a); - flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); - } + if (!ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); } - } +/* Wait until all workers are running */ 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; +void flecs_wait_for_workers( + ecs_world_t *world) +{ + ecs_poly_assert(world, ecs_world_t); - 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; - } + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } - return flecs_table_ensure(world, &dst_type, true, node); - } else if (idr->flags & EcsIdExclusive) { - /* Relationship is exclusive, check if table already has it */ - const 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->column] = with; - return flecs_table_ensure(world, &dst_type, true, node); - } + bool wait = true; + do { + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_running == (stage_count - 1)) { + wait = false; } - } else { - idr = flecs_id_record_ensure(world, with); - r = with; + 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; } - /* 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 */ + 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); } - 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); - } - } + /* 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); - 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); + 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; } - return flecs_table_ensure(world, &dst_type, true, node); + 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); } -static -ecs_table_t* flecs_find_table_without( - ecs_world_t *world, - ecs_table_t *node, - ecs_id_t without) +void flecs_join_worker_threads( + ecs_world_t *world) { - 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); + 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; } - } + }; - /* 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 */ + /* If no threads are active, just return */ + if (!threads_active) { + return; } - return flecs_table_ensure(world, &dst_type, true, node); -} + /* Make sure all threads are running, to ensure they catch the signal */ + flecs_wait_for_workers(world); -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; + /* 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); } -static -void flecs_init_edge_for_add( +/* -- Private functions -- */ +void flecs_workers_progress( ecs_world_t *world, - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time) { - 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; + ecs_poly_assert(world, ecs_world_t); + ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); - edge->hdr.next = next; - if (next) { - next->prev = &edge->hdr; - } + /* Make sure workers are running and ready */ + flecs_wait_for_workers(world); - flecs_compute_table_diff(world, table, to, edge, id); - } + /* 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_init_edge_for_remove( +void flecs_set_threads_internal( ecs_world_t *world, - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) + int32_t threads, + bool use_task_api) { - flecs_table_init_edge(table, edge, id, to); - - flecs_table_ensure_hi_edge(world, &table->node.remove, id); + ecs_assert(threads <= 1 || (use_task_api + ? ecs_os_has_task_support() + : ecs_os_has_threading()), + ECS_MISSING_OS_API, NULL); - 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; + int32_t stage_count = ecs_get_stage_count(world); + bool worker_method_changed = (use_task_api != world->workers_use_task_api); - to_refs->prev = &edge->hdr; - edge->hdr.next = to_refs; + 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); - edge->hdr.prev = prev; - if (prev) { - prev->next = &edge->hdr; + 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); + } } - flecs_compute_table_diff(world, table, to, edge, id); + 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); + } } } -static -ecs_table_t* flecs_create_edge_for_remove( +/* -- Public functions -- */ + +void ecs_set_threads( ecs_world_t *world, - ecs_table_t *node, - ecs_graph_edge_t *edge, - ecs_id_t id) + int32_t threads) { - ecs_table_t *to = flecs_find_table_without(world, node, id); - flecs_init_edge_for_remove(world, node, edge, id, to); - return to; + flecs_set_threads_internal(world, threads, false /* use thread API */); } -static -ecs_table_t* flecs_create_edge_for_add( +void ecs_set_task_threads( ecs_world_t *world, - ecs_table_t *node, - ecs_graph_edge_t *edge, - ecs_id_t id) + int32_t task_threads) { - ecs_table_t *to = flecs_find_table_with(world, node, id); - flecs_init_edge_for_add(world, node, edge, id, to); - return to; + flecs_set_threads_internal(world, task_threads, true /* use task API */); } -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) +bool ecs_using_task_threads( + ecs_world_t *world) { - ecs_poly_assert(world, ecs_world_t); + return world->workers_use_task_api; +} - node = node ? node : &world->store.root; +#endif - /* 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); + /** + * @file addons/rules/api.c + * @brief User facing API for rules. + */ - 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; + /** + * @file addons/rules/rules.h + * @brief Internal types and functions for rules addon. + */ - 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; - } - } +#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 */ + const char *name; /* variable name */ +#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) */ + EcsRuleWith, /* Match id against fixed or variable source */ + EcsRuleAndAny, /* And operator with support for matching Any src/id */ + EcsRuleTrav, /* Support for transitive/reflexive queries */ + 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 (*) */ + EcsRuleUnion, /* Combine output of multiple operations */ + EcsRuleEnd, /* Used to denote end of EcsRuleUnion block */ + EcsRuleNot, /* Sets iterator state after term was not matched */ + 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 */ + EcsRuleSetVars, /* Populate it.sources from variables */ + EcsRuleSetThis, /* Populate This entity variable */ + EcsRuleSetFixed, /* Set fixed source entity ids */ + EcsRuleSetIds, /* Set fixed (component) ids */ + EcsRuleContain, /* Test if table contains entity */ + EcsRulePairEq, /* Test if both elements of pair are the same */ + EcsRuleSetCond, /* Set conditional value for EcsRuleJmpCondFalse */ + EcsRuleJmpCondFalse, /* Jump if condition is false */ + EcsRuleJmpNotSet, /* Jump if variable(s) is not set */ + 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; + +/* Cache for storing results of downward traversal */ +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; + +/* Ctrlflow context (used with Union) */ +typedef struct { + ecs_rule_lbl_t lbl; +} ecs_rule_ctrlflow_ctx_t; + +/* Condition context */ +typedef struct { + bool cond; +} ecs_rule_cond_ctx_t; + +typedef struct ecs_rule_op_ctx_t { + union { + ecs_rule_and_ctx_t and; + 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_ctrlflow_ctx_t ctrlflow; + ecs_rule_cond_ctx_t cond; + } is; +} ecs_rule_op_ctx_t; + +typedef struct { + /* Labels used for control flow */ + ecs_rule_lbl_t lbl_union; + ecs_rule_lbl_t lbl_not; + ecs_rule_lbl_t lbl_option; + ecs_rule_lbl_t lbl_cond_eval; + ecs_rule_lbl_t lbl_or; + ecs_rule_lbl_t lbl_none; + ecs_rule_lbl_t lbl_prev; /* If set, use this as default value for prev */ + 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_rule_lbl_t prev_index; /* Previously evaluated operation */ + ecs_rule_lbl_t jump; /* Set by control flow operations to jump to 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_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); - return to; -error: - return NULL; -} +/* Get allocator from iterator */ +ecs_allocator_t* flecs_rule_get_allocator( + const ecs_iter_t *it); -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); +/* Find all entities when traversing downwards */ +void flecs_rule_get_down_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity); - node = node ? node : &world->store.root; +/* Find all entities when traversing upwards */ +void flecs_rule_get_up_cache( + const ecs_rule_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table); - /* 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); +/* Free traversal cache */ +void flecs_rule_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache); - 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; +#endif - 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); - } +#include - 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; - } +#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) } +}; - return to; -error: - return NULL; +static +const char* flecs_rule_op_str( + uint16_t kind) +{ + switch(kind) { + case EcsRuleAnd: return "and "; + case EcsRuleAndId: return "and_id "; + case EcsRuleAndAny: return "andany "; + case EcsRuleWith: return "with "; + case EcsRuleTrav: return "trav "; + case EcsRuleIdsRight: return "idsr "; + case EcsRuleIdsLeft: return "idsl "; + case EcsRuleEach: return "each "; + case EcsRuleStore: return "store "; + case EcsRuleReset: return "reset "; + case EcsRuleUnion: return "union "; + 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 EcsRuleSetVars: return "setvars "; + case EcsRuleSetThis: return "setthis "; + case EcsRuleSetFixed: return "setfix "; + case EcsRuleSetIds: return "setids "; + case EcsRuleContain: return "contain "; + case EcsRulePairEq: return "pair_eq "; + case EcsRuleSetCond: return "setcond "; + case EcsRuleJmpCondFalse: return "jfalse "; + case EcsRuleJmpNotSet: return "jnotset "; + case EcsRuleYield: return "yield "; + case EcsRuleNothing: return "nothing "; + default: return "!invalid"; + } } -ecs_table_t* flecs_table_find_or_create( - ecs_world_t *world, - ecs_type_t *type) +/* 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(world, ecs_world_t); - return flecs_table_ensure(world, type, false, NULL); + 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)); + } } -void flecs_init_root_table( - ecs_world_t *world) +static +void flecs_rule_fini( + ecs_rule_t *rule) { - ecs_poly_assert(world, ecs_world_t); + if (rule->vars != &rule->vars_cache.var) { + ecs_os_free(rule->vars); + } - 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); + 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); - /* 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; + ecs_poly_free(rule, ecs_rule_t); } -void flecs_table_clear_edges( - ecs_world_t *world, - ecs_table_t *table) +void ecs_rule_fini( + ecs_rule_t *rule) { - (void)world; - ecs_poly_assert(world, ecs_world_t); - - ecs_log_push_1(); + 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_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; +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); - /* 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)); + /* 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; } - it = ecs_map_iter(remove_hi); - while (ecs_map_next(&it)) { - flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); - } + result->iterable.init = flecs_rule_iter_mixin_init; - /* 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)); + /* Compile filter to operations */ + if (flecs_rule_compile(world, stage, result)) { + goto error; } - /* 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)); - } + ecs_entity_t entity = const_desc->entity; + result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; - 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); + if (entity) { + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t); + poly->poly = result; + ecs_poly_modified(world, entity, ecs_rule_t); } - 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(); + return result; +error: + ecs_rule_fini(result); + return NULL; } -/* 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) +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) { - ecs_table_diff_t diff; - return flecs_table_traverse_add(world, table, &id, &diff); + 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; } -ecs_table_t* ecs_table_remove_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) +char* ecs_rule_str_w_profile( + const ecs_rule_t *rule, + const ecs_iter_t *it) { - ecs_table_diff_t diff; - return flecs_table_traverse_remove(world, table, &id, &diff); -} + ecs_poly_assert(rule, ecs_rule_t); -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_id_t*)ids, - .count = id_count - }; - return flecs_table_ensure(world, &type, false, NULL); -} + 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); -/** - * @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. - */ + 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 + } -#include + 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 --; + } -/* Utility macros to enforce consistency when initializing iterator fields */ + ecs_strbuf_append(&buf, "%*s", indent, ""); + ecs_strbuf_appendstr(&buf, flecs_rule_op_str(op->kind)); + ecs_strbuf_appendstr(&buf, " "); -/* 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;\ - } + int32_t written = ecs_strbuf_written(&buf); + for (int32_t j = 0; j < (10 - (written - start)); j ++) { + ecs_strbuf_appendch(&buf, ' '); + } -/* 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);\ - } + if (op->kind == EcsRuleJmpCondFalse || op->kind == EcsRuleSetCond || + op->kind == EcsRuleJmpNotSet) + { + ecs_strbuf_appendint(&buf, op->other); + ecs_strbuf_appendch(&buf, ' '); + } + + hidden_chars = flecs_rule_op_ref_str(rule, &op->src, src_flags, &buf); -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); -} + if (op->kind == EcsRuleUnion) { + indent ++; + } -void flecs_iter_free( - void *ptr, - ecs_size_t size) -{ - flecs_stack_free(ptr, size); -} + if (!first_flags && !second_flags) { + ecs_strbuf_appendstr(&buf, "\n"); + continue; + } -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); + written = ecs_strbuf_written(&buf) - hidden_chars; + for (int32_t j = 0; j < (30 - (written - start)); j ++) { + ecs_strbuf_appendch(&buf, ' '); + } - ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); - ecs_stack_t *stack = &stage->allocators.iter_stack; + ecs_strbuf_appendstr(&buf, "("); + flecs_rule_op_ref_str(rule, &op->first, first_flags, &buf); - 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); + 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; + } + default: + break; + } + } - 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); -} + ecs_strbuf_appendch(&buf, ')'); -void flecs_iter_validate( - ecs_iter_t *it) -{ - ECS_BIT_SET(it->flags, EcsIterIsValid); + ecs_strbuf_appendch(&buf, '\n'); + } - /* 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; +#ifdef FLECS_LOG + char *str = ecs_strbuf_get(&buf); + flecs_colorize_buf(str, true, &buf); + ecs_os_free(str); +#endif + return ecs_strbuf_get(&buf); } -void ecs_iter_fini( - ecs_iter_t *it) +char* ecs_rule_str( + const ecs_rule_t *rule) { - 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); + return ecs_rule_str_w_profile(rule, NULL); +} - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_stack_restore_cursor(&stage->allocators.iter_stack, - &it->priv.cache.stack_cursor); +const ecs_filter_t* ecs_rule_get_filter( + const ecs_rule_t *rule) +{ + return &rule->filter; } -static -bool flecs_iter_populate_term_data( - ecs_world_t *world, +const char* ecs_rule_parse_vars( + ecs_rule_t *rule, ecs_iter_t *it, - int32_t t, - int32_t column, - void **ptr_out) + const char *expr) { - 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; + 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); } - /* Filter terms may match with data but don't return it */ - if (it->terms[t].inout == EcsInOutNone) { - goto no_data; + ptr = ecs_parse_ws_eol(ptr); + if (!ptr[0]) { + return ptr; } - ecs_assert(it->sizes != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t size = it->sizes[t]; - if (!size) { - goto no_data; + if (ptr[0] == '(') { + paren = true; + ptr = ecs_parse_ws_eol(ptr + 1); + if (ptr[0] == ')') { + return ptr + 1; + } } - if (column < 0) { - table = it->table; - is_shared = true; + do { + ptr = ecs_parse_ws_eol(ptr); + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } - /* 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])) { + int var = ecs_rule_find_var(rule, token); + if (var == -1) { + ecs_parser_error(name, expr, (ptr - expr), + "unknown variable '%s'", token); + return NULL; + } - /* 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; - } - } + ptr = ecs_parse_ws_eol(ptr); + if (ptr[0] != ':') { + ecs_parser_error(name, expr, (ptr - expr), + "missing ':'"); + return NULL; + } - if (!ref->id) { - is_shared = false; - } + ptr = ecs_parse_ws_eol(ptr + 1); + ptr = ecs_parse_identifier(name, expr, ptr, token); + if (!ptr) { + return NULL; + } - return is_shared; + 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; } - return true; + 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_entity_t subj = it->sources[t]; - ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); + ecs_parser_error(name, expr, (ptr - expr), + "expected , or end of string"); + return NULL; + } + } while (true); - /* 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); + return ptr; +error: + return NULL; +} - row = ECS_RECORD_TO_ROW(r->row); - table = r->table; +#endif - ecs_id_t id = it->ids[t]; - ecs_table_t *s_table = table->storage_table; - ecs_table_record_t *tr; + /** + * @file addons/rules/compile.c + * @brief Compile rule program from filter. + */ - if (!s_table || !(tr = flecs_table_record_get(world, s_table, id))){ - 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 = 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); +#ifdef FLECS_RULES - row = it->offset; +#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ - int32_t storage_column = ecs_table_type_to_storage_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; - } +static bool flecs_rule_op_is_test[] = { + [EcsRuleAnd] = true, + [EcsRuleAndAny] = true, + [EcsRuleAndId] = true, + [EcsRuleWith] = true, + [EcsRuleTrav] = true, + [EcsRuleContain] = true, + [EcsRulePairEq] = true, + [EcsRuleNothing] = false +}; - if (!it->count) { - goto no_data; - } +ecs_rule_lbl_t flecs_itolbl(int64_t val) { + return flecs_ito(int16_t, val); +} - ecs_vec_t *s = &table->data.columns[storage_column]; - data = ecs_vec_first(s); +static +ecs_var_id_t flecs_itovar(int64_t val) { + return flecs_ito(uint8_t, val); +} - /* Fallthrough to has_data */ - } +static +ecs_var_id_t flecs_utovar(uint64_t val) { + return flecs_uto(uint8_t, val); +} -has_data: - if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); - return is_shared; +#ifdef FLECS_DEBUG +#define flecs_set_var_label(var, lbl) (var)->label = lbl +#else +#define flecs_set_var_label(var, lbl) +#endif -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; +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; + } } - -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) +bool flecs_rule_is_written( + ecs_var_id_t var_id, + uint64_t written) { - ecs_table_t *prev_table = it->table; - if (prev_table) { - it->frame_offset += ecs_table_count(prev_table); + if (var_id == EcsVarNone) { + return true; } - 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; + 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); } } +} - int t, field_count = it->field_count; - if (ECS_BIT_IS_SET(it->flags, EcsIterNoData)) { - ECS_BIT_CLEAR(it->flags, EcsIterHasShared); - return; - } +ecs_flags16_t flecs_rule_ref_flags( + ecs_flags16_t flags, + ecs_flags16_t kind) +{ + return (flags >> kind) & (EcsRuleIsVar | EcsRuleIsEntity); +} - 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]); +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); } - - ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); + return false; } -bool flecs_iter_next_row( - ecs_iter_t *it) +static +bool flecs_rule_var_is_anonymous( + const ecs_rule_t *rule, + ecs_var_id_t var_id) { - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_rule_var_t *var = &rule->vars[var_id]; + return var->anonymous; +} - 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; +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); - if (instance_count > count && offset < (instance_count - 1)) { - ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); - int t, field_count = it->field_count; + /* Backwards compatibility */ + if (!ecs_os_strcmp(name, "This")) { + name = "this"; + } - for (t = 0; t < field_count; t ++) { - int32_t column = it->columns[t]; - if (column >= 0) { - void *ptr = it->ptrs[t]; - if (ptr) { - it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); - } - } + if (kind == EcsVarTable) { + if (!ecs_os_strcmp(name, EcsThisName)) { + if (rule->has_table_this) { + return 0; + } else { + return EcsVarNone; } + } - if (it->entities) { - it->entities ++; - } - it->offset ++; + if (!flecs_name_index_is_init(&rule->tvar_index)) { + return EcsVarNone; + } - return true; + uint64_t index = flecs_name_index_find( + &rule->tvar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; } + return flecs_utovar(index); } - return false; -} + if (kind == EcsVarEntity) { + if (!flecs_name_index_is_init(&rule->evar_index)) { + return EcsVarNone; + } -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; + uint64_t index = flecs_name_index_find( + &rule->evar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; + } + return flecs_utovar(index); } - return result; -} - -/* --- Public API --- */ - -void* ecs_field_w_size( - const ecs_iter_t *it, - size_t size, - int32_t term) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->ptrs != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!size || ecs_field_size(it, term) == size || - (!ecs_field_size(it, term) && (!it->ptrs[term - 1])), - ECS_INVALID_PARAMETER, NULL); - (void)size; + ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); - if (!term) { - return it->entities; + /* 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 it->ptrs[term - 1]; -error: - return NULL; + return flecs_rule_find_var_id(rule, name, EcsVarTable); } -bool ecs_field_is_readonly( - const ecs_iter_t *it, - int32_t term_index) +int32_t ecs_rule_var_count( + const ecs_rule_t *rule) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); - - ecs_term_t *term = &it->terms[term_index - 1]; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - - if (term->inout == EcsIn) { - return true; - } else { - ecs_term_id_t *src = &term->src; + return rule->var_pub_count; +} - if (term->inout == EcsInOutDefault) { - if (!(ecs_term_match_this(term))) { - return true; +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 (!(src->flags & EcsSelf)) { - return true; + if (!ecs_os_strcmp(name, EcsThisName)) { + var_id = 0; } } + if (var_id == EcsVarNone) { + return -1; + } } + return (int32_t)var_id; +} -error: - return false; +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_field_is_writeonly( - const ecs_iter_t *it, - int32_t term_index) +bool ecs_rule_var_is_entity( + const ecs_rule_t *rule, + int32_t var_id) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); + return rule->vars[var_id].kind == EcsVarEntity; +} - ecs_term_t *term = &it->terms[term_index - 1]; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - - if (term->inout == EcsOut) { - return true; +static +const char* flecs_term_id_var_name( + ecs_term_id_t *term_id) +{ + if (!(term_id->flags & EcsIsVariable)) { + return NULL; } -error: + 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; } -bool ecs_field_is_set( - const ecs_iter_t *it, - int32_t index) +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) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); + 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; + } - 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; + var_id = flecs_rule_find_var_id(rule, name, EcsVarTable); + if (var_id != EcsVarNone) { + return var_id; + } + + kind = EcsVarTable; } else { - return true; + 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; + if (vars) { + var = ecs_vec_append_t(NULL, vars, ecs_rule_var_t); + 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]; + var->id = flecs_itovar(rule->var_count); + rule->var_count ++; } - return true; -error: - return false; -} + var->kind = flecs_ito(int8_t, kind); + var->name = name; + var->table_id = var_id; + flecs_set_var_label(var, NULL); -bool ecs_field_is_self( - const ecs_iter_t *it, - int32_t index) -{ - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - return it->sources == NULL || it->sources[index - 1] == 0; -} + if (name) { + flecs_name_index_init_if(var_index, NULL); + flecs_name_index_ensure(var_index, var->id, name, 0, 0); + var->anonymous = name ? name[0] == '_' : false; + } -ecs_id_t ecs_field_id( - const ecs_iter_t *it, - int32_t index) -{ - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - return it->ids[index - 1]; + return var->id; } -int32_t ecs_field_column_index( - const ecs_iter_t *it, - int32_t index) +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) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - - int32_t result = it->columns[index - 1]; - if (result <= 0) { - return -1; + const char *name = flecs_term_id_var_name(term_id); + if (!name) { + return EcsVarNone; } - return result - 1; + return flecs_rule_add_var(rule, name, vars, kind); } -ecs_entity_t ecs_field_src( - const ecs_iter_t *it, - int32_t index) +static +void flecs_rule_discover_vars( + ecs_stage_t *stage, + ecs_rule_t *rule) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - if (it->sources) { - return it->sources[index - 1]; - } else { - return 0; - } -} + ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ + ecs_vec_reset_t(NULL, vars, ecs_rule_var_t); -size_t ecs_field_size( - const ecs_iter_t *it, - int32_t index) -{ - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_term_t *terms = rule->filter.terms; + int32_t 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; - if (index == 0) { - return sizeof(ecs_entity_t); - } else { - return (size_t)it->sizes[index - 1]; - } -} + /* 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; -char* ecs_iter_str( - const ecs_iter_t *it) -{ - ecs_world_t *world = it->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - int i; + 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 (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); + 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_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_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 */ + } - 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"); + /* If first is a wildcard, insert anonymous variable */ + if (flecs_term_id_is_wildcard(first)) { + anonymous_count ++; } } - ecs_strbuf_list_pop(&buf, "\n"); - } - if (it->variable_count) { - 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; - } + 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); - ecs_var_t var = it->variables[i]; - if (!var.entity) { - /* Skip table variables */ - continue; - } + /* 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); + var->kind = EcsVarAny; - if (!actual_count) { - ecs_strbuf_list_push(&buf, "var: ", ","); - } + anonymous_table_count ++; + } - char *str = ecs_get_fullpath(world, var.entity); - ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); - ecs_os_free(str); + if (var_id != EcsVarNone) { + /* Track of 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); + } - actual_count ++; - } - if (actual_count) { - ecs_strbuf_list_pop(&buf, "\n"); + 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 (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); + 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 ++; + } } - } - return ecs_strbuf_get(&buf); -} + 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 ++; + } + } -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); -} + /* 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; + } + } + } -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 var_count = ecs_vec_count(vars); -int32_t ecs_iter_count( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + /* 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_BIT_SET(it->flags, EcsIterNoData); - ECS_BIT_SET(it->flags, EcsIterIsInstanced); + 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 ++; + } + } - int32_t count = 0; - while (ecs_iter_next(it)) { - count += it->count; + var_count = ecs_vec_count(vars); } - 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); + /* Always include spot for This variable, even if rule doesn't use it */ + var_count ++; - ecs_entity_t result = 0; - if (ecs_iter_next(it)) { - result = it->entities[0]; - ecs_iter_fini(it); + 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)); } - return result; -error: - return 0; -} - -bool ecs_iter_is_true( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + rule->vars = rule_vars; + rule->var_count = var_count; + rule->var_pub_count = var_count; + rule->has_table_this = !entity_before_table_this; - ECS_BIT_SET(it->flags, EcsIterNoData); - ECS_BIT_SET(it->flags, EcsIterIsInstanced); +#ifdef FLECS_DEBUG + rule->var_size = var_count + anonymous_count; +#endif - bool result = ecs_iter_next(it); - if (result) { - ecs_iter_fini(it); - } - return result; -error: - return false; -} + char **var_names = ECS_ELEM(rule_vars, ECS_SIZEOF(ecs_rule_var_t), + var_count + anonymous_count); + rule->var_names = (char**)var_names; -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); + 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; + var_names[0] = ECS_CONST_CAST(char*, rule_vars[0].name); + rule_vars ++; + var_names ++; + var_count --; - 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) { - 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]; - } + 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); } - } else { - ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); } - return e; -error: - return 0; + /* Hide anonymous table variables from application */ + rule->var_pub_count -= anonymous_table_count; } -ecs_table_t* ecs_iter_get_var_as_table( - ecs_iter_t *it, - int32_t var_id) +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) { - 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) { - /* 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 (kind == EcsVarTable || kind == EcsVarEntity) { + return flecs_rule_find_var_id(rule, name, kind); } - if (table) { - if (var->range.offset) { - /* Don't return whole table if only partial table is matched */ - return NULL; - } + 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 (!var->range.count || ecs_table_count(table) == var->range.count) { - /* Return table if count matches */ - return table; - } + 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; } -error: - return NULL; + /* If table var is written, and entity var doesn't exist or is not written, + * return table var */ + ecs_assert(tvar != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + return tvar; } -ecs_table_range_t ecs_iter_get_var_as_range( - ecs_iter_t *it, - int32_t var_id) +static +ecs_rule_lbl_t flecs_rule_op_insert( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx) { - 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) { - 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); + 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_union == -1) { + /* Variables written by previous instruction can't be written by + * this instruction, except when this is a union. */ + elem->written &= ~elem[-1].written; } } - 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; + if (ctx->cur->lbl_union != -1) { + elem->prev = ctx->cur->lbl_union; + } else if (ctx->cur->lbl_prev != -1) { + elem->prev = ctx->cur->lbl_prev; + ctx->cur->lbl_prev = -1; } else { - var->range.table = NULL; - var->range.offset = 0; - var->range.count = 0; + elem->prev = flecs_itolbl(count - 2); + } + + elem->next = flecs_itolbl(count); + return flecs_itolbl(count - 1); +} + +static +int32_t flecs_rule_not_insert( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t *op_last = ecs_vec_last_t(ctx->ops, ecs_rule_op_t); + if (op_last && op_last->kind == EcsRuleNot) { + /* There can be multiple reasons for inserting a Not operation, ensure + * that only one is created. */ + ecs_assert(op_last->field_index == op->field_index, + ECS_INTERNAL_ERROR, NULL); + return ecs_vec_count(ctx->ops) - 1; } - it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); + ecs_rule_op_t not_op = {0}; + not_op.kind = EcsRuleNot; + not_op.field_index = op->field_index; + not_op.first = op->first; + not_op.second = op->second; + not_op.flags = op->flags; + not_op.flags &= (ecs_flags8_t)~(EcsRuleIsVar << EcsRuleSrc); + if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { + not_op.src.entity = op->src.entity; + } -error: - return; + return flecs_rule_op_insert(¬_op, ctx); } -void ecs_iter_set_var_as_table( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_t *table) +static +void flecs_rule_begin_none( + ecs_rule_compile_ctx_t *ctx) { - ecs_table_range_t range = { .table = (ecs_table_t*)table }; - ecs_iter_set_var_as_range(it, var_id, &range); + ctx->cur->lbl_none = flecs_itolbl(ecs_vec_count(ctx->ops)); + + ecs_rule_op_t jmp = {0}; + jmp.kind = EcsRuleJmpCondFalse; + flecs_rule_op_insert(&jmp, ctx); } -void ecs_iter_set_var_as_range( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_range_t *range) +static +void flecs_rule_begin_not( + ecs_rule_compile_ctx_t *ctx) { - 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); + ctx->cur->lbl_not = flecs_itolbl(ecs_vec_count(ctx->ops)); +} - /* Can't set variable while iterating */ - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, NULL); +static +void flecs_rule_end_not( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx, + bool update_labels) +{ + if (ctx->cur->lbl_none != -1) { + ecs_rule_op_t setcond = {0}; + setcond.kind = EcsRuleSetCond; + setcond.other = ctx->cur->lbl_none; + flecs_rule_op_insert(&setcond, ctx); + } - ecs_var_t *var = &it->variables[var_id]; - var->range = *range; + flecs_rule_not_insert(op, ctx); - 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; + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + int32_t i, count = ecs_vec_count(ctx->ops); + if (update_labels) { + for (i = ctx->cur->lbl_not; i < count; i ++) { + ecs_rule_op_t *cur = &ops[i]; + if (flecs_rule_op_is_test[cur->kind]) { + cur->prev = flecs_itolbl(count - 1); + if (i == (count - 2)) { + cur->next = flecs_itolbl(ctx->cur->lbl_not - 1); + } + } + } } - it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); + /* After a match was found, return to op before Not operation */ + ecs_rule_op_t *not_ptr = ecs_vec_last_t(ctx->ops, ecs_rule_op_t); + not_ptr->prev = flecs_itolbl(ctx->cur->lbl_not - 1); -error: - return; -} + if (ctx->cur->lbl_none != -1) { + /* setcond */ + ops[count - 2].next = flecs_itolbl(ctx->cur->lbl_none - 1); + /* last actual instruction */ + if (update_labels) { + ops[count - 3].prev = flecs_itolbl(count - 4); + } + /* jfalse */ + ops[ctx->cur->lbl_none].other = + flecs_itolbl(count - 1); /* jump to not */ + /* not */ + ops[count - 1].prev = flecs_itolbl(ctx->cur->lbl_none - 1); + } -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; + ctx->cur->lbl_not = -1; + ctx->cur->lbl_none = -1; } static -void ecs_chained_iter_fini( - ecs_iter_t *it) +void flecs_rule_begin_option( + ecs_rule_compile_ctx_t *ctx) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_iter_fini(it->chain_it); + ctx->cur->lbl_option = flecs_itolbl(ecs_vec_count(ctx->ops)); - it->chain_it = NULL; + { + ecs_rule_op_t new_op = {0}; + new_op.kind = EcsRuleJmpCondFalse; + flecs_rule_op_insert(&new_op, ctx); + } } -ecs_iter_t ecs_page_iter( - const ecs_iter_t *it, - int32_t offset, - int32_t limit) +static +void flecs_rule_end_option( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_rule_not_insert(op, ctx); + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + int32_t count = ecs_vec_count(ctx->ops); - ecs_iter_t result = *it; - 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_iter_t*)it; + ops[ctx->cur->lbl_option].other = flecs_itolbl(count - 1); + ops[count - 2].next = flecs_itolbl(count); - return result; -error: - return (ecs_iter_t){ 0 }; + ecs_rule_op_t new_op = {0}; + new_op.kind = EcsRuleSetCond; + new_op.other = ctx->cur->lbl_option; + flecs_rule_op_insert(&new_op, ctx); + + ctx->cur->lbl_option = -1; } static -void flecs_offset_iter( - ecs_iter_t *it, - int32_t offset) +void flecs_rule_begin_cond_eval( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx, + ecs_write_flags_t cond_write_state) { - 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; - } + ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; + ecs_write_flags_t cond_mask = 0; - if (it->sources[t]) { - continue; - } + if (flecs_rule_ref_flags(op->flags, EcsRuleFirst) == EcsRuleIsVar) { + first_var = op->first.var; + cond_mask |= (1ull << first_var); + } + if (flecs_rule_ref_flags(op->flags, EcsRuleSecond) == EcsRuleIsVar) { + second_var = op->second.var; + cond_mask |= (1ull << second_var); + } + if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) == EcsRuleIsVar) { + src_var = op->src.var; + cond_mask |= (1ull << src_var); + } - it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); - } + /* 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; } -} -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); + /* 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_iter_t *chain_it = it->chain_it; - bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + ecs_rule_op_t jmp_op = {0}; + jmp_op.kind = EcsRuleJmpNotSet; - do { - if (!ecs_iter_next(chain_it)) { - goto depleted; + 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; } - 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)); + flecs_rule_op_insert(&jmp_op, ctx); + } else { + ctx->cur->lbl_cond_eval = -1; + } +} - /* Keep instancing setting from original iterator */ - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); +static +void flecs_rule_end_cond_eval( + ecs_rule_op_t *op, + ecs_rule_compile_ctx_t *ctx) +{ + if (ctx->cur->lbl_cond_eval == -1) { + return; + } - if (!chain_it->table) { - goto yield; /* Task query */ - } + flecs_rule_not_insert(op, ctx); - int32_t offset = iter->offset; - int32_t limit = iter->limit; - if (!(offset || limit)) { - if (it->count) { - goto yield; - } else { - goto depleted; - } - } + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + int32_t count = ecs_vec_count(ctx->ops); + ops[ctx->cur->lbl_cond_eval].other = flecs_itolbl(count - 1); + ops[count - 2].next = flecs_itolbl(count); +} - int32_t count = it->count; - int32_t remaining = iter->remaining; +static +void flecs_rule_insert_reset_after_or( + ecs_rule_compile_ctx_t *ctx) +{ + /* 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. */ + int32_t i; + 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 (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 (!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); } + } +} - 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); +static +void flecs_rule_next_or( + ecs_rule_compile_ctx_t *ctx) +{ + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + int32_t count = ecs_vec_count(ctx->ops); + ecs_rule_op_t *op = &ops[count - 1]; -yield: - if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { - it->offset = 0; + if (op->kind == EcsRuleNot) { + /* If the engine reaches a Not operation, it means that the term in the + * chain was negated. This can happen when part(s) of the term have + * conditionally set variables: if a variable was not set this will + * cause the term to automatically evaluate to false. + * In this case, the Not operation should proceed to the next term in + * the chain, whereas the preceding operation should upon success skip + * to the end of the chain, which is what the marker is replaced with. */ + ops[count - 2].next = FlecsRuleOrMarker; + } else { + ops[count - 1].next = FlecsRuleOrMarker; } +} - return true; -done: - /* Cleanup iterator resources if it wasn't yet depleted */ - ecs_iter_fini(chain_it); -depleted: -error: - return false; +static +void flecs_rule_begin_or( + ecs_rule_compile_ctx_t *ctx, + ecs_rule_lbl_t lbl_start) +{ + ctx->cur->lbl_or = flecs_itolbl(lbl_start); + flecs_rule_next_or(ctx); } -bool ecs_page_next( - ecs_iter_t *it) +static +void flecs_rule_end_or( + ecs_rule_compile_ctx_t *ctx) { - 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); + flecs_rule_next_or(ctx); - ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + int32_t i, count = ecs_vec_count(ctx->ops); + int32_t prev_or = -2; - if (flecs_iter_next_row(it)) { - return true; + for (i = ctx->cur->lbl_or; i < count; i ++) { + if (ops[i].next == FlecsRuleOrMarker) { + if (prev_or != -2) { + ops[prev_or].prev = flecs_itolbl(i); + } + ops[i].next = flecs_itolbl(count); + prev_or = i; + } } - return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); -error: - return false; -} + ecs_rule_op_t *op = &ops[count - 1]; + op->prev = flecs_itolbl(ctx->cur->lbl_or - 1); + + if (op->kind == EcsRuleNot) { + /* If last op is a Not operation it means that this was a dependent term + * which should be ignored if one or more of its variables weren't set + * yet. In addition to setting the prev label of the Not operation, also + * set the prev label of the actual operation to the start of the OR + * chain. */ + op[-1].prev = flecs_itolbl(ctx->cur->lbl_or - 1); + } -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); + /* Set prev of next instruction to before the start of the OR chain */ + ctx->cur->lbl_prev = flecs_itolbl(ctx->cur->lbl_or - 1); + ctx->cur->lbl_or = -1; - ecs_iter_t result = *it; - 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_iter_t*)it; + flecs_rule_insert_reset_after_or(ctx); - return result; -error: - return (ecs_iter_t){ 0 }; + ctx->ctrlflow->in_or = false; } static -bool ecs_worker_next_instanced( - ecs_iter_t *it) +void flecs_rule_begin_union( + ecs_rule_compile_ctx_t *ctx) { - 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_rule_op_t op = {0}; + op.kind = EcsRuleUnion; + ctx->cur->lbl_union = flecs_rule_op_insert(&op, ctx); +} - 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; +static +void flecs_rule_end_union( + ecs_rule_compile_ctx_t *ctx) +{ + flecs_rule_next_or(ctx); - do { - if (!ecs_iter_next(chain_it)) { - return false; + ecs_rule_op_t op = {0}; + op.kind = EcsRuleEnd; + ctx->cur->lbl_union = -1; + ecs_rule_lbl_t next = flecs_rule_op_insert(&op, ctx); + + ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); + int32_t i = ecs_vec_count(ctx->ops) - 2; + for (; i >= 0 && (ops[i].kind != EcsRuleUnion); i --) { + if (ops[i].next == FlecsRuleOrMarker) { + ops[i].next = next; } + } - /* 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; + ops[next].prev = flecs_itolbl(i); + ops[i].next = next; - if (count) { - if (res_index < count) { - per_worker ++; - first += res_index; - } else { - first += count; - } - } + flecs_rule_insert_reset_after_or(ctx); - if (!per_worker && it->table == NULL) { - if (res_index == 0) { - return true; - } else { - return false; - } - } - } while (!per_worker); + ctx->ctrlflow->in_or = false; +} - it->instance_count = instances_per_worker; - it->frame_offset += first; +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); +} - flecs_offset_iter(it, it->offset + first); - it->count = per_worker; +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); - if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { - it->offset += first; - } else { - it->offset = 0; - } + /* 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); - return true; -error: - return false; + /* 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); } -bool ecs_worker_next( - ecs_iter_t *it) +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) { - 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); + /* 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)); - if (flecs_iter_next_row(it)) { - return true; + 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); - return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); -error: - return false; + ecs_rule_ref_t r = { .var = evar }; + op->first = r; + op->flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); + op->flags |= (EcsRuleIsVar << EcsRuleFirst); } -/** - * @file misc.c - * @brief Miscallaneous 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) +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) { - union { - uint64_t u; - int64_t s; - } v; + (void)world; - v.u = u; + if (!ecs_term_id_is_set(term_id)) { + return; + } - 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); + 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); + } else { + 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); } - return u; + if (term_id->flags & EcsIsEntity) { + op->flags |= (ecs_flags8_t)(EcsRuleIsEntity << ref_kind); + ref->entity = term_id->id; + } } -#endif -int32_t flecs_next_pow_of_2( - int32_t n) +static +bool 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) { - n --; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n ++; + ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); + bool written = false; - return n; -} + 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)) { + flecs_rule_insert_each(tvar, var_id, ctx, cond_write); -/** 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; -} + /* Variable was written, just not as entity */ + written = true; + } + } -ecs_time_t ecs_time_sub( - ecs_time_t t1, - ecs_time_t t2) -{ - ecs_time_t result; + written |= flecs_rule_is_written(var_id, ctx->written); - if (t1.nanosec >= t2.nanosec) { - result.nanosec = t1.nanosec - t2.nanosec; - result.sec = t1.sec - t2.sec; + /* After evaluating a term, a used variable is always written */ + flecs_rule_write(var_id, &op->written); + flecs_rule_write_ctx(var_id, ctx, cond_write); } else { - result.nanosec = t1.nanosec - t2.nanosec + 1000000000; - result.sec = t1.sec - t2.sec - 1; + /* If it's not a variable, it's always written */ + written = true; } - return result; + return written; } -void ecs_sleepf( - double t) +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) { - if (t > 0) { - int sec = (int)t; - int nsec = (int)((t - sec) * 1000000000); - ecs_os_sleep(sec, nsec); + 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); } } -double ecs_time_measure( - ecs_time_t *start) +static +void flecs_rule_insert_pair_eq( + int32_t field_index, + ecs_rule_compile_ctx_t *ctx) { - 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); + ecs_rule_op_t contains = {0}; + contains.kind = EcsRulePairEq; + contains.field_index = flecs_ito(int8_t, field_index); + flecs_rule_op_insert(&contains, ctx); } -void* ecs_os_memdup( - const void *src, - ecs_size_t size) +static +bool flecs_rule_term_fixed_id( + ecs_filter_t *filter, + ecs_term_t *term) { - if (!src) { - return NULL; + /* Transitive/inherited terms have variable ids */ + if (term->flags & (EcsTermTransitive|EcsTermIdInherited)) { + return false; } - - 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); -} + /* Or terms can match different ids */ + if (term->oper == EcsOr) { + return false; + } + if ((term != filter->terms) && term[-1].oper == EcsOr) { + return false; + } -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; -} + /* Wildcards can assume different ids */ + if (ecs_id_is_wildcard(term->id)) { + return false; + } -char* ecs_vasprintf( - const char *fmt, - va_list args) -{ - ecs_size_t size = 0; - char *result = NULL; - va_list tmpa; + /* Any terms can have fixed ids, but they require special handling */ + if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + return false; + } - va_copy(tmpa, args); + /* First terms that are Not or Optional require special handling */ + if (term->oper == EcsNot || term->oper == EcsOptional) { + if (term == filter->terms) { + return false; + } + } - size = vsnprintf(result, 0, fmt, tmpa); + return true; +} - va_end(tmpa); +static +int flecs_rule_compile_builtin_pred( + ecs_term_t *term, + ecs_rule_op_t *op, + ecs_write_flags_t write_state) +{ + ecs_entity_t id = term->first.id; - if ((int32_t)size < 0) { - return NULL; + 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]); } - result = (char *) ecs_os_malloc(size + 1); + 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 (!result) { - return NULL; + if (flags_2nd & EcsRuleIsVar) { + if (!(write_state & (1ull << op->second.var))) { + ecs_err("uninitialized variable '%s' on right-hand side of " + "equality operator", term->second.name); + return -1; + } } - ecs_os_vsprintf(result, fmt, args); + return 0; +} - return result; +static +void flecs_rule_compile_push( + ecs_rule_compile_ctx_t *ctx) +{ + ctx->cur = &ctx->ctrlflow[++ ctx->scope]; + ctx->cur->lbl_union = -1; + ctx->cur->lbl_prev = -1; + ctx->cur->lbl_not = -1; + ctx->cur->lbl_none = -1; } -char* ecs_asprintf( - const char *fmt, - ...) +static +void flecs_rule_compile_pop( + ecs_rule_compile_ctx_t *ctx) { - va_list args; - va_start(args, fmt); - char *result = ecs_vasprintf(fmt, args); - va_end(args); - return result; + ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } -char* flecs_to_snake_case(const char *str) { - int32_t upper_count = 0, len = 1; - const char *ptr = str; - char ch, *out, *out_ptr; +static +int flecs_rule_compile_term( + ecs_world_t *world, + ecs_rule_t *rule, + ecs_term_t *term, + ecs_rule_compile_ctx_t *ctx) +{ + bool first_term = term == rule->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 = (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); + bool cond_write = term->oper == EcsOptional || is_or; + ecs_rule_op_t op = {0}; - for (ptr = &str[1]; (ch = *ptr); ptr ++) { - if (isupper(ch)) { - upper_count ++; - } - len ++; + if (is_or && (first_term || term[-1].oper != EcsOr)) { + ctx->ctrlflow->cond_written_or = ctx->cond_written; + ctx->ctrlflow->in_or = true; } - 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 ++; + 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 (term->oper == EcsNot) { + ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); + } else { + ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + } + } else if (term->first.id == EcsScopeClose) { + flecs_rule_compile_pop(ctx); + if (ctx->scope_is_not & (ecs_flags32_t)(1ull << ctx->scope)) { + op.field_index = -1; + flecs_rule_end_not(&op, ctx, false); } - out_ptr[0] = (char)tolower(ch); - out_ptr ++; } else { - out_ptr[0] = ch; - out_ptr ++; + /* Nothing to be done */ } + return 0; } - out_ptr[0] = '\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 - rule->filter.terms); - return out; -} + /* 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 { + if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + op.kind = EcsRuleAndAny; + } + } -/** - * @file value.c - * @brief Utility functions to work with non-trivial pointers of user types. - */ + /* If term has fixed id, insert simpler instruction that skips dealing with + * wildcard terms and variables */ + if (flecs_rule_term_fixed_id(&rule->filter, term)) { + if (op.kind == EcsRuleAnd) { + op.kind = EcsRuleAndId; + } + } + /* 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; + ecs_write_flags_t write_state = ctx->written; -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; + /* Resolve component inheritance if necessary */ + ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; - ecs_xtor_t ctor; - if ((ctor = ti->hooks.ctor)) { - ctor(ptr, 1, ti); - } else { - ecs_os_memset(ptr, 0, ti->size); - } + /* Resolve variables and entities for operation arguments */ + flecs_rule_compile_term_id(world, rule, &op, &term->first, + &op.first, EcsRuleFirst, EcsVarEntity, ctx); + flecs_rule_compile_term_id(world, rule, &op, &term->second, + &op.second, EcsRuleSecond, EcsVarEntity, ctx); - return 0; -error: - return -1; -} + if (first_is_var) first_var = op.first.var; + if (second_is_var) second_var = op.second.var; -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; -} + /* Insert each instructions for table -> entity variable if needed */ + bool first_written = flecs_rule_compile_ensure_vars( + rule, &op, &op.first, EcsRuleFirst, ctx, cond_write); + bool second_written = flecs_rule_compile_ensure_vars( + rule, &op, &op.second, EcsRuleSecond, ctx, cond_write); -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; + /* Do src last, in case it uses the same variable as first/second */ + flecs_rule_compile_term_id(world, rule, &op, &term->src, + &op.src, EcsRuleSrc, EcsVarAny, ctx); + if (src_is_var) src_var = op.src.var; + bool src_written = flecs_rule_is_written(src_var, ctx->written); - 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; + /* Cache the current value of op.first. This value may get overwritten with + * a variable when term has component inheritance, but Not operations may + * need the original value to initialize the result id with. */ + ecs_rule_ref_t prev_first = op.first; + ecs_flags8_t prev_op_flags = op.flags; + + /* 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) { + 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; + } } - 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"); + /* 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); + src_var = op.src.var; + } + } - return ecs_value_new_w_type_info(world, ti); -error: - return NULL; -} + flecs_rule_compile_ensure_vars(rule, &op, &op.src, EcsRuleSrc, ctx, cond_write); -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; + /* If source is Any (_) and first and/or second are unconstrained, insert an + * ids instruction instead of an And */ + if (term->flags & EcsTermMatchAnySrc) { + /* 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) */ + op.kind = EcsRuleIdsLeft; + } else { + /* If second is wildcard, traverse right: (r, *) -> */ + 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); + } + } - ecs_xtor_t dtor; - if ((dtor = ti->hooks.dtor)) { - dtor(ptr, 1, ti); + /* 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); + } } - return 0; -error: - return -1; -} + /* If term has component inheritance enabled, insert instruction to walk + * down the relationship tree of the id. */ + if (term->flags & EcsTermIdInherited) { + if (is_not) { + /* Ensure that term only matches if none of the inherited ids match + * with the source. */ + flecs_rule_begin_none(ctx); + } + flecs_rule_insert_inheritance(rule, term, &op, ctx, cond_write); + } -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; -} + /* If previous term was ScopeOpen with a Not operator, insert operation to + * ensure that none of the results inside the scope should match. */ + if (!first_term && term[-1].first.id == EcsScopeOpen) { + if (term[-1].oper == EcsNot) { + flecs_rule_begin_none(ctx); + flecs_rule_begin_not(ctx); + } + flecs_rule_compile_push(ctx); + } -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; + /* Handle Not, Optional, Or operators */ + if (is_not) { + flecs_rule_begin_not(ctx); + } else if (term->oper == EcsOptional) { + flecs_rule_begin_option(ctx); + } else if (term->oper == EcsOr) { + if (first_term || term[-1].oper != EcsOr) { + if (!src_written) { + flecs_rule_begin_union(ctx); + } + } } - flecs_free(&world->allocator, ti->size, ptr); + /* Track label before inserting operations for current */ + ecs_rule_lbl_t lbl_start = flecs_itolbl(ecs_vec_count(ctx->ops)); - return 0; -error: - return -1; -} + /* Check if this term has variables that have been conditionally written, + * like variables written by an optional term. */ + if (ctx->cond_written) { + flecs_rule_begin_cond_eval(&op, ctx, cond_write_state); + } -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; + op.match_flags = term->flags; - ecs_copy_t copy; - if ((copy = ti->hooks.copy)) { - copy(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); + if (first_is_var) { + op.first.var = first_var; + op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); + op.flags |= (EcsRuleIsVar << EcsRuleFirst); } - return 0; -error: - return -1; -} + if (term->src.flags & EcsSelf) { + op.flags |= EcsRuleIsSelf; + } -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; -} + if (builtin_pred) { + if (flecs_rule_compile_builtin_pred(term, &op, write_state)) { + 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; + flecs_rule_op_insert(&op, ctx); - ecs_move_t move; - if ((move = ti->hooks.move)) { - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); + /* 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); + } } - 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; -} + /* 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); + } -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; + /* Handle closing of conditional evaluation */ + if (ctx->cond_written && (first_is_var || second_is_var || src_is_var)) { + flecs_rule_end_cond_eval(&op, ctx); + } - ecs_move_t move; - if ((move = ti->hooks.move_ctor)) { - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); + /* Handle closing of Not, Optional and Or operators */ + if (is_not) { + /* Restore original first id in case it got replaced with a variable */ + op.first = prev_first; + op.flags = prev_op_flags; + flecs_rule_end_not(&op, ctx, true); + } else if (term->oper == EcsOptional) { + flecs_rule_end_option(&op, ctx); + } else if (term->oper == EcsOr) { + if (ctx->cur->lbl_union != -1) { + flecs_rule_next_or(ctx); + } else { + if (first_term || term[-1].oper != EcsOr) { + if (ctx->cur->lbl_union == -1) { + flecs_rule_begin_or(ctx, lbl_start); + } + } else if (term->oper == EcsOr) { + flecs_rule_next_or(ctx); + } + } + } else if (term->oper == EcsAnd) { + if (!first_term && term[-1].oper == EcsOr) { + if (ctx->cur->lbl_union != -1) { + flecs_rule_end_union(ctx); + } else { + flecs_rule_end_or(ctx); + } + } } return 0; -error: - return -1; } -int ecs_value_move_ctor( - const ecs_world_t *world, - ecs_entity_t type, - void* dst, - void *src) +int flecs_rule_compile( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_rule_t *rule) { - 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 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. - */ - - -/* -- Identifier Component -- */ -static ECS_DTOR(EcsIdentifier, ptr, { - ecs_os_strset(&ptr->value, NULL); -}) + 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_union = -1; + ctx.cur->lbl_prev = -1; + ctx.cur->lbl_not = -1; + ctx.cur->lbl_none = -1; + ctx.cur->lbl_or = -1; + ctx.cur->lbl_union = -1; + ecs_vec_clear(ctx.ops); -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; -}) + /* Find all variables defined in query */ + flecs_rule_discover_vars(stage, rule); -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; + /* If rule contains fixed source terms, insert operation to set sources */ + int32_t i, count = filter->term_count; + for (i = 0; i < 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; + } + } - src->value = NULL; - src->hash = 0; - src->index_hash = 0; - src->index = 0; - src->length = 0; -}) + /* 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 < count; i ++) { + ecs_term_t *term = &terms[i]; + if (flecs_rule_term_fixed_id(filter, term)) { + ecs_rule_op_t set_ids = {0}; + set_ids.kind = EcsRuleSetIds; + flecs_rule_op_insert(&set_ids, &ctx); + break; + } + } -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; + /* Compile query terms to instructions */ + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (flecs_rule_compile_term(world, rule, term, &ctx)) { + return -1; + } + } - 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 This variable has been written as entity, insert an operation to + * assign it to it.entities for consistency. */ + ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); + 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); + } - if (evt == EcsOnSet) { - index = flecs_id_name_index_ensure(world, pair); - } else { - index = flecs_id_name_index_get(world, pair); + /* 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); + } + } + } } } - int i, count = it->count; + /* 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 < count; i ++) { - EcsIdentifier *cur = &ptr[i]; - uint64_t hash; - ecs_size_t len; - const char *name = cur->value; + for (i = 0; i < filter->field_count; i ++) { + ecs_var_id_t var_id = rule->src_vars[i]; + if (!var_id) { + continue; + } - 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 (!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; + } + } + } } - 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; + /* 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); } - if (index) { - uint64_t index_hash = cur->index_hash; - ecs_entity_t e = it->entities[i]; + for (i = 0; i < filter->field_count; i ++) { + ecs_var_id_t var_id = rule->src_vars[i]; + if (!var_id) { + continue; + } - 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); + 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 (!rule->filter.term_count) { + ecs_rule_op_t nothing = {0}; + nothing.kind = EcsRuleNothing; + flecs_rule_op_insert(¬hing, &ctx); + } else { + /* 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); + } -/* -- Poly component -- */ + return 0; +} -static ECS_COPY(EcsPoly, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); -}) +#endif -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); - } +/** + * @file addons/rules/engine.c + * @brief Rules engine implementation. + */ - 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); +#ifdef FLECS_RULES + +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]; +} -/* -- Builtin triggers -- */ +#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) static -void flecs_assert_relation_unused( - ecs_world_t *world, - ecs_entity_t rel, - ecs_entity_t property) +ecs_table_range_t flecs_range_from_entity( + ecs_entity_t e, + const ecs_rule_run_ctx_t *ctx) { - 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; - } + 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 + }; +} - bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); - if (property != EcsUnion) { - in_use |= ecs_id_in_use(world, rel); +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; } - 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); + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(entity, ctx); + return var->range; } -error: - return; + return (ecs_table_range_t){ 0 }; } static -bool flecs_set_id_flag( - ecs_id_record_t *idr, - ecs_flags32_t flag) +ecs_table_t* flecs_rule_var_get_table( + int32_t var_id, + const ecs_rule_run_ctx_t *ctx) { - if (!(idr->flags & flag)) { - idr->flags |= flag; - return true; + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return table; } - return false; + + 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 -bool flecs_unset_id_flag( - ecs_id_record_t *idr, - ecs_flags32_t flag) +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) { - if ((idr->flags & flag)) { - idr->flags &= ~flag; - return true; + 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); } - 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_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_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); + 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 -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; - } +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_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); +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_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_OBJECT_FLAG(ECS_PAIR_SECOND(id)), - EcsIdOnDeleteObjectMask, - EcsEntityIsId); +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_register_traversable(ecs_iter_t *it) { - flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdTraversable, - EcsIdTraversable, 0); +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_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]; +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 (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)); + 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_pair_first(ctx->world, 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_pair_second(ctx->world, id), ctx); } } } static -void flecs_register_exclusive(ecs_iter_t *it) { - flecs_register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, - EcsIdExclusive, 0); +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 -void flecs_register_dont_inherit(ecs_iter_t *it) { - flecs_register_id_flag_for_relation(it, EcsDontInherit, - EcsIdDontInherit, EcsIdDontInherit, 0); +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 -void flecs_register_always_override(ecs_iter_t *it) { - flecs_register_id_flag_for_relation(it, EcsAlwaysOverride, - EcsIdAlwaysOverride, EcsIdAlwaysOverride, 0); +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 ecs_get_alive(ctx->world, first); + } } static -void flecs_register_with(ecs_iter_t *it) { - flecs_register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); +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 -void flecs_register_union(ecs_iter_t *it) { - flecs_register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); +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_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); +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; + if (it->sources[field_index] != 0) { + it->columns[field_index] *= -1; } } static -void flecs_on_symmetric_add_remove(ecs_iter_t *it) { - ecs_entity_t pair = ecs_field_id(it, 1); +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]; +} - if (!ECS_HAS_ID_FLAG(pair, PAIR)) { - /* If relationship was not added as a pair, there's nothing to do */ +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_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); + 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_select_w_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx, + ecs_id_t id) +{ + 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 (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } } -} -static -void flecs_register_symmetric(ecs_iter_t *it) { - ecs_world_t *world = it->real_world; + if (!redo || !op_ctx->remaining) { + tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } - 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); + 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 --; + } - /* 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} - }); - } + flecs_rule_set_match(op, table, op_ctx->column, ctx); + return true; } static -void flecs_on_component(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsComponent *c = ecs_field(it, EcsComponent, 1); +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); +} - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; +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; - 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; + ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); + if (!table) { + return false; + } - 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)); + 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; } - } 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; + tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } - 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); + 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); } -} -/* -- Iterable mixins -- */ + flecs_rule_set_match(op, table, op_ctx->column, ctx); + return true; +} 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) +bool flecs_rule_and( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - ecs_iter_poly(world, poly, it, filter); - it->event_id = filter->id; + 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); + } } -/* -- 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) +bool flecs_rule_select_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_vec_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, record, false, false); - record->row = ECS_ROW_TO_RECORD(index, 0); + 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); - EcsComponent *component = ecs_vec_first(&columns[0]); - component[index].size = size; - component[index].alignment = alignment; + 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; + } + } - const char *name = &symbol[3]; /* Strip 'Ecs' */ - ecs_size_t symbol_length = ecs_os_strlen(symbol); - ecs_size_t name_length = symbol_length - 3; + if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + return false; + } + } - EcsIdentifier *name_col = ecs_vec_first(&columns[1]); - 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); + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } - EcsIdentifier *symbol_col = ecs_vec_first(&columns[2]); - 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; + ecs_table_t *table = tr->hdr.table; + flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); + flecs_rule_it_set_column(it, field, tr->index); + return true; } -/** 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) +bool flecs_rule_with_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - /* 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; + if (redo) { + return false; + } - /* 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_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_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_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); + if (!table) { + return false; + } - ecs_type_t array = { - .array = ids, - .count = 6 - }; + 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_t *result = flecs_table_find_or_create(world, &array); - ecs_data_t *data = &result->data; + ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + return false; + } - /* 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->records, ecs_record_t*, EcsFirstUserComponentId); - ecs_vec_init_t(a, &data->columns[0], EcsComponent, EcsFirstUserComponentId); - ecs_vec_init_t(a, &data->columns[1], EcsIdentifier, EcsFirstUserComponentId); - ecs_vec_init_t(a, &data->columns[2], EcsIdentifier, EcsFirstUserComponentId); - - return result; + flecs_rule_it_set_column(it, field, tr->index); + return true; } static -void flecs_bootstrap_entity( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - ecs_entity_t parent) +bool flecs_rule_and_id( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - 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); + 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); } } -void flecs_bootstrap( - ecs_world_t *world) +static +bool flecs_rule_and_any( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - 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 }); + ecs_flags16_t match_flags = op->match_flags; + if (redo) { + if (match_flags & EcsTermMatchAnySrc) { + return false; + } + } - /* Create and cache often used id records on world */ - flecs_init_id_records(world); + 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; + } - /* 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); + 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); + } + } - /* 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); + int32_t field = op->field_index; + if (field != -1) { + ctx->it->ids[field] = flecs_rule_op_get_id(op, ctx); + } - /* 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; + return result; +} - /* 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 - }); +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_entity_t *entities = table->data.entities.array; + int32_t count = range->count; + if (!count) { + count = ecs_table_count(table); + } - /* Populate core module */ - ecs_set_scope(world, EcsFlecsCore); + 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_bootstrap_tag(world, EcsName); - flecs_bootstrap_tag(world, EcsSymbol); - flecs_bootstrap_tag(world, EcsAlias); + flecs_rule_set_trav_match(op, -1, trav, second, ctx); + return true; +} - flecs_bootstrap_tag(world, EcsQuery); - flecs_bootstrap_tag(world, EcsObserver); +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; +} - 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); +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 */ + } - /* Initialize builtin modules */ - ecs_set_name(world, EcsFlecs, "flecs"); - ecs_add_id(world, EcsFlecs, EcsModule); - ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); + 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; - ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); - ecs_set_name(world, EcsFlecsCore, "core"); - ecs_add_id(world, EcsFlecsCore, EcsModule); + /* 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; + } + } - ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); - ecs_set_name(world, EcsFlecsInternals, "internals"); - ecs_add_id(world, EcsFlecsInternals, EcsModule); + flecs_rule_set_trav_match(op, column, trav, second, ctx); + return true; +} - /* 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; +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); - /* 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); + 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; - /* 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); + /* 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); + } - 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); + /* Entity is traversable, which means it could have a subtree */ + flecs_rule_get_down_cache(ctx, &trav_ctx->cache, trav, second); + trav_ctx->index = 0; - flecs_bootstrap_tag(world, EcsFlatten); - flecs_bootstrap_tag(world, EcsDefaultChildComponent); + 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); + } + } - /* 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); + if (trav_ctx->index == -1) { + redo = false; /* First result after handling reflexive relationship */ + trav_ctx->index = 0; + } - /* Builtin relationships */ - flecs_bootstrap_tag(world, EcsIsA); - flecs_bootstrap_tag(world, EcsChildOf); - flecs_bootstrap_tag(world, EcsDependsOn); + /* 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))) { + return true; + } + + redo = false; + } - /* 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); + return false; +} - /* 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); +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); - /* 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); + 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; + } - /* 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); + 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); - /* Create triggers in internals scope */ - ecs_set_scope(world, EcsFlecsInternals); + /* 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); + } - /* Term used to also match prefabs */ - ecs_term_t match_prefab = { - .id = EcsPrefab, - .oper = EcsOptional, - .src.flags = EcsSelf - }; + return true; +} - /* 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 - }); +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); - ecs_observer(world, { - .filter.terms = { - { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_on_delete - }); + if (!redo) { + flecs_rule_get_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 ++; + } - ecs_observer(world, { - .filter.terms = { - { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_on_delete_object - }); + 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; + } - ecs_observer(world, { - .filter.terms = { - { .id = EcsTraversable, .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_traversable - }); + if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { + return false; + } - ecs_observer(world, { - .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_exclusive - }); + 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; +} - ecs_observer(world, { - .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = flecs_register_symmetric - }); +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]; - ecs_observer(world, { - .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = flecs_register_dont_inherit - }); + 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"); + return false; + } 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); + } + } +} - ecs_observer(world, { - .filter.terms = {{ .id = EcsAlwaysOverride, .src.flags = EcsSelf } }, - .events = {EcsOnAdd}, - .callback = flecs_register_always_override - }); +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; - ecs_observer(world, { - .filter.terms = { - { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, - match_prefab - }, - .events = {EcsOnAdd}, - .callback = flecs_register_with - }); + 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; + } - ecs_observer(world, { - .filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, - .events = {EcsOnAdd}, - .callback = flecs_register_union - }); + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; + } - /* 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 - }); + cur = op_ctx->cur = cur->first.next; + } else { + if (!op_ctx->cur) { + return false; + } - /* 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 - }); + cur = op_ctx->cur = op_ctx->cur->first.next; + } - /* Set scope back to flecs core */ - ecs_set_scope(world, EcsFlecsCore); + if (!cur) { + return false; + } - /* Traversable relationships are always acyclic */ - ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); + flecs_rule_set_vars(op, cur->id, ctx); - /* Transitive relationships are always Traversable */ - ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); + 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; + } - /* DontInherit components */ - ecs_add_id(world, EcsPrefab, EcsDontInherit); + return true; +} - /* Acyclic/Traversable components */ - ecs_add_id(world, EcsIsA, EcsTraversable); - ecs_add_id(world, EcsDependsOn, EcsTraversable); - ecs_add_id(world, EcsWith, EcsAcyclic); +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; - /* Transitive relationships */ - ecs_add_id(world, EcsIsA, EcsTransitive); - ecs_add_id(world, EcsIsA, EcsReflexive); + 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; + } - /* Exclusive properties */ - ecs_add_id(world, EcsSlotOf, EcsExclusive); - ecs_add_id(world, EcsOneOf, EcsExclusive); - ecs_add_id(world, EcsFlatten, EcsExclusive); - - /* Run bootstrap functions for other parts of the code */ - flecs_bootstrap_hierarchy(world); + cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + if (!cur) { + return false; + } - ecs_set_scope(world, 0); + cur = op_ctx->cur = cur->second.next; + } else { + if (!op_ctx->cur) { + return false; + } - ecs_set_name_prefix(world, NULL); + cur = op_ctx->cur = op_ctx->cur->second.next; + } - ecs_log_pop(); -} + if (!cur) { + return false; + } -/** - * @file hierarchy.c - * @brief API for entity paths and name lookups. - */ + flecs_rule_set_vars(op, cur->id, ctx); -#include + 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; + } -#define ECS_NAME_BUFFER_LENGTH (64) + return true; +} 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) +bool flecs_rule_each( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - 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); - } - } + ecs_rule_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); + int32_t row; - const EcsIdentifier *id = ecs_get_pair( - world, child, EcsIdentifier, EcsName); - if (id) { - name = id->value; - name_len = id->length; - } + 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 (name) { - ecs_strbuf_appendstrn(buf, name, name_len); + if (!redo) { + row = op_ctx->row = range.offset; } else { - ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); + 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; + } } - return cur != 0; + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entities = table->data.entities.array; + ecs_entity_t e; + do { + e = entities[row ++]; + + /* 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; } -bool flecs_name_is_id( - const char *name) +static +bool flecs_rule_store( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!isdigit(name[0])) { + if (!redo) { + flecs_rule_var_set_entity(op, op->src.var, op->first.entity, ctx); + return true; + } else { 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) +static +bool flecs_rule_reset( + const ecs_rule_op_t *op, + bool redo, + const ecs_rule_run_ctx_t *ctx) { - 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; + if (!redo) { + return true; } else { - if ((uint32_t)result == (uint64_t)result) { - return (ecs_entity_t)result; - } else { - return 0; - } + flecs_rule_var_reset(op->src.var, ctx); + return false; } } static -ecs_entity_t flecs_get_builtin( - const char *name) +bool flecs_rule_union( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) { - 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; + if (!redo) { + ctx->jump = flecs_itolbl(ctx->op_index + 1); + return true; + } else { + ecs_rule_lbl_t next = flecs_itolbl(ctx->prev_index + 1); + if (next == op->next) { + return false; + } + ctx->jump = next; + return true; } - - return 0; } static -bool flecs_is_sep( - const char **ptr, - const char *sep) +bool flecs_rule_end( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) { - ecs_size_t len = ecs_os_strlen(sep); + (void)op; - if (!ecs_os_strncmp(*ptr, sep, len)) { - *ptr += len; + ecs_rule_ctrlflow_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrlflow); + if (!redo) { + op_ctx->lbl = ctx->prev_index; return true; } else { - return false; + ctx->jump = op_ctx->lbl; + return true; } } static -const char* flecs_path_elem( - const char *path, - const char *sep, - int32_t *len) +bool flecs_rule_not( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) { - const char *ptr; - char ch; - int32_t template_nesting = 0; - int32_t count = 0; + if (redo) { + return false; + } - for (ptr = path; (ch = *ptr); ptr ++) { - if (ch == '<') { - template_nesting ++; - } else if (ch == '>') { - template_nesting --; - } + int32_t field = op->field_index; + if (field == -1) { + return true; + } - ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); + ecs_iter_t *it = ctx->it; - if (!template_nesting && flecs_is_sep(&ptr, sep)) { - break; - } + /* Not terms return no data */ + it->columns[field] = 0; - count ++; + /* Ignore variables written by Not operation */ + uint64_t *written = ctx->written; + uint64_t written_cur = written[ctx->op_index] = 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; } - if (len) { - *len = count; + /* 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 (count) { - return ptr; - } else { - return NULL; + /* If term has entity src, set it because no other instruction might */ + if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { + it->sources[field] = op->src.entity; } -error: - return NULL; + + return true; /* Flip result */ } static -bool flecs_is_root_path( - const char *path, - const char *prefix) +const char* flecs_rule_name_arg( + const ecs_rule_op_t *op, + ecs_rule_run_ctx_t *ctx) { - if (prefix) { - return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); - } else { - return false; - } + int8_t term_index = op->term_index; + ecs_term_t *term = &ctx->rule->filter.terms[term_index]; + return term->second.name; } 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 flecs_rule_compare_range( + const ecs_table_range_t *l, + const ecs_table_range_t *r) { - 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 (l->table != r->table) { + return false; } - if (!start_from_root && !parent && new_entity) { - parent = ecs_get_scope(world); + 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 */ } - *path_ptr = path; - - return parent; + return true; } static -void flecs_on_set_symbol(ecs_iter_t *it) { - EcsIdentifier *n = ecs_field(it, EcsIdentifier, 1); - ecs_world_t *world = it->world; +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; + } - 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); + 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; + 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; } } -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 - }); +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; + } -/* Public functions */ + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_eq_w_range(op, redo, ctx, r); +} -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) +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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); + 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 (child == EcsWildcard) { - ecs_strbuf_appendch(buf, '*'); - return; - } - if (child == EcsAny) { - ecs_strbuf_appendch(buf, '_'); - return; + /* If tables don't match, neq always returns once */ + if (l.table != r.table) { + return true && !redo; } - if (!sep) { - sep = "."; - } + 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); + } - if (!child || parent != child) { - flecs_path_append(world, parent, child, sep, prefix, buf); + l_offset = l.offset; + l_count = l.count; + + /* Cache old value */ + op_ctx->range = l; } else { - ecs_strbuf_appendstrn(buf, "", 0); + l_offset = op_ctx->range.offset; + l_count = op_ctx->range.count; } -error: - return; -} + /* 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; + } -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); + /* 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; + } } -ecs_entity_t ecs_lookup_child( - const ecs_world_t *world, - ecs_entity_t parent, - const char *name) +static +bool flecs_rule_pred_match( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx, + bool is_neq) { - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + 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; - 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_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; } - } - 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); + 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 { - return 0; - } -error: - return 0; -} + if (op_ctx->name_col == -1) { + /* Table has no name */ + return false; + } -ecs_entity_t ecs_lookup( - const ecs_world_t *world, - const char *name) -{ - if (!name) { - return 0; + l = op_ctx->range; } - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + 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; + } - ecs_entity_t e = flecs_get_builtin(name); - if (e) { - return e; + if (!result) { + if (offset != -1) { + break; + } + } else { + if (offset == -1) { + offset = op_ctx->index; + } + } } - if (flecs_name_is_id(name)) { - return flecs_name_to_id(world, name); + if (offset == -1) { + ctx->vars[first_var].range = op_ctx->range; + return false; } - e = flecs_name_index_find(&world->aliases, name, 0, 0); - if (e) { - return e; - } - - return ecs_lookup_child(world, 0, name); -error: - return 0; + ctx->vars[first_var].range.offset = offset; + ctx->vars[first_var].range.count = (op_ctx->index - offset); + return true; } -ecs_entity_t ecs_lookup_symbol( - const ecs_world_t *world, - const char *name, - bool lookup_as_path) -{ - if (!name) { - return 0; - } - - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); +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); +} - ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); - if (e) { - return e; - } +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); +} - if (lookup_as_path) { - return ecs_lookup_fullpath(world, name); - } +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"); -error: - return 0; + 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); } -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) +static +bool flecs_rule_pred_neq_name( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) { - if (!path) { - return 0; + 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_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; - } + ecs_table_range_t r = flecs_range_from_entity(e, ctx); + return flecs_rule_pred_neq_w_range(op, redo, ctx, r); +} - e = flecs_name_index_find(&world->aliases, path, 0, 0); - if (e) { - return e; - } +static +bool flecs_rule_setvars( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + (void)op; - 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_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; - ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); - ecs_entity_t *lookup_path_cur = lookup_path; - while (lookup_path_cur && *lookup_path_cur) { - lookup_path_cur ++; + if (redo) { + return false; } - if (!sep) { - sep = "."; - } + int32_t i; + for (i = 0; i < filter->field_count; i ++) { + ecs_var_id_t var_id = src_vars[i]; + if (!var_id) { + continue; + } - parent = flecs_get_parent_from_path(stage, parent, &path, prefix, true); + it->sources[i] = flecs_rule_var_get_entity(var_id, ctx); - if (!sep[0]) { - return ecs_lookup_child(world, parent, path); + int32_t column = it->columns[i]; + if (column > 0) { + it->columns[i] = -column; + } } -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; - } + return true; +} - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; - } +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]; - elem[len] = '\0'; - ptr_start = ptr; + if (!redo) { + /* Save values so we can restore them later */ + op_ctx->range = vars[0].range; - cur = ecs_lookup_child(world, cur, elem); - if (!cur) { - goto tail; - } + /* 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; } +} -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; - } - } +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 (lookup_path_search) { - if (lookup_path_cur != lookup_path) { - lookup_path_cur --; - parent = lookup_path_cur[0]; - goto retry; - } - } + if (redo) { + return false; } - if (elem != buff) { - ecs_os_free(elem); + 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 cur; -error: - return 0; + return true; } -ecs_entity_t ecs_set_scope( - ecs_world_t *world, - ecs_entity_t scope) +static +bool flecs_rule_setids( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + (void)op; + const ecs_rule_t *rule = ctx->rule; + const ecs_filter_t *filter = &rule->filter; + ecs_iter_t *it = ctx->it; - ecs_entity_t cur = stage->scope; - stage->scope = scope; + if (redo) { + return false; + } - return cur; -error: - return 0; -} + 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; + } -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; + return true; } -ecs_entity_t* ecs_set_lookup_path( - ecs_world_t *world, - const ecs_entity_t *lookup_path) +/* 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) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + if (redo) { + return false; + } - ecs_entity_t *cur = stage->lookup_path; - stage->lookup_path = (ecs_entity_t*)lookup_path; + ecs_var_id_t src_id = op->src.var; + ecs_var_id_t first_id = op->first.var; - return cur; -error: - return NULL; + 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); } -ecs_entity_t* ecs_get_lookup_path( - const ecs_world_t *world) +/* 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) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->lookup_path; -error: - return NULL; -} + if (redo) { + return false; + } -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; + 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 -void flecs_add_path( - ecs_world_t *world, - bool defer_suspend, - ecs_entity_t parent, - ecs_entity_t entity, - const char *name) +bool flecs_rule_jmp_if_not( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) { - 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 (!redo) { + flecs_op_ctx(ctx, cond)->cond = false; + return true; + } else { + if (!flecs_op_ctx(ctx, cond)->cond) { + ctx->jump = op->other; + } + return false; } +} - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, parent); +static +bool flecs_rule_jmp_set_cond( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (!redo) { + ctx->op_ctx[op->other].is.cond.cond = true; + return true; + } else { + return false; } +} - ecs_set_name(world, entity, name); +static +bool flecs_rule_jmp_not_set( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) +{ + if (!redo) { + ecs_var_t *vars = ctx->vars; + if (flecs_rule_ref_flags(op->flags, EcsRuleFirst) == EcsRuleIsVar) { + if (vars[op->first.var].entity == EcsWildcard) { + ctx->jump = op->other; + return true; + } + } + if (flecs_rule_ref_flags(op->flags, EcsRuleSecond) == EcsRuleIsVar) { + if (vars[op->second.var].entity == EcsWildcard) { + ctx->jump = op->other; + return true; + } + } + if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) == EcsRuleIsVar) { + if (vars[op->src.var].entity == EcsWildcard) { + ctx->jump = op->other; + return true; + } + } - if (defer_suspend) { - flecs_resume_readonly(real_world, &srs); - flecs_defer_path((ecs_stage_t*)world, parent, entity, name); + return true; + } else { + return false; } } -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) +static +bool flecs_rule_run( + const ecs_rule_op_t *op, + bool redo, + ecs_rule_run_ctx_t *ctx) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!sep) { - sep = "."; + 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 EcsRuleWith: return flecs_rule_with(op, redo, ctx); + case EcsRuleTrav: return flecs_rule_trav(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 EcsRuleUnion: return flecs_rule_union(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 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 EcsRuleContain: return flecs_rule_contain(op, redo, ctx); + case EcsRulePairEq: return flecs_rule_pair_eq(op, redo, ctx); + case EcsRuleJmpCondFalse: return flecs_rule_jmp_if_not(op, redo, ctx); + case EcsRuleSetCond: return flecs_rule_jmp_set_cond(op, redo, ctx); + case EcsRuleJmpNotSet: return flecs_rule_jmp_not_set(op, redo, ctx); + case EcsRuleYield: return false; + case EcsRuleNothing: return false; } + return false; +} - if (!path) { - if (!entity) { - entity = ecs_new_id(world); - } +static +void flecs_rule_iter_init( + ecs_rule_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + if (ctx->written) { + 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 (parent) { - ecs_add_pair(world, entity, EcsChildOf, entity); - } + if (!(it_written & (1ull << var_id)) || + (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) + { + continue; + } - return entity; + /* 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 */ + } + } } - 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; + flecs_iter_validate(it); +} - 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; - } +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); - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; - } + if (flecs_iter_next_row(it)) { + return true; + } - elem[len] = '\0'; - ptr_start = ptr; + return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); +error: + return false; +} - ecs_entity_t e = ecs_lookup_child(world, cur, elem); - if (!e) { - if (name) { - ecs_os_free(name); - } +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); - name = ecs_os_strdup(elem); + ecs_rule_iter_t *rit = &it->priv.iter.rule; + bool redo = it->flags & EcsIterIsValid; + ecs_rule_lbl_t next; - /* 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; - } + 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.prev_index = -1; + ctx.jump = -1; + ctx.op_ctx = rit->op_ctx; + const ecs_rule_op_t *ops = rit->ops; - 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 (!(it->flags & EcsIterIsValid)) { + if (!ctx.rule) { + goto done; + } + flecs_rule_iter_init(&ctx); + } - if (!cur && last_elem && root_path) { - ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); - } + do { + ctx.op_index = rit->op; + const ecs_rule_op_t *op = &ops[ctx.op_index]; - flecs_add_path(world, suspend_defer, cur, e, name); - } +#ifdef FLECS_DEBUG + rit->profile[ctx.op_index].count[redo] ++; +#endif - cur = e; - } + bool result = flecs_rule_run(op, redo, &ctx); + ctx.prev_index = ctx.op_index; - if (entity && (cur != entity)) { - ecs_throw(ECS_ALREADY_DEFINED, name); + next = (&op->prev)[result]; + if (ctx.jump != -1) { + next = ctx.jump; + ctx.jump = -1; } - if (name) { - ecs_os_free(name); + if ((next > ctx.op_index)) { + ctx.written[next] |= ctx.written[ctx.op_index] | op->written; } - if (elem != buff) { - ecs_os_free(elem); - } - } else { - flecs_add_path(world, suspend_defer, parent, entity, path); - } + redo = next < ctx.prev_index; + rit->op = next; - return cur; -error: - return 0; -} + if (op->kind == EcsRuleYield) { + ecs_table_range_t *range = &rit->vars[0].range; + ecs_table_t *table = range->table; + if (table && !range->count) { + range->count = ecs_table_count(table); + } + flecs_iter_populate_data(ctx.world, it, range->table, range->offset, + range->count, it->ptrs); + return true; + } + } while (next >= 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); +done: + ecs_iter_fini(it); + return false; } -/** - * @file id_record.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) +void flecs_rule_iter_fini_ctx( + ecs_iter_t *it, + ecs_rule_iter_t *rit) { - return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); + 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; + default: + break; + } + } } static -void flecs_id_record_elem_insert( - ecs_id_record_t *head, - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) +void flecs_rule_iter_fini( + ecs_iter_t *it) { - 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; + 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); } - head_elem->next = idr; + + 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; } -static -void flecs_id_record_elem_remove( - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) +ecs_iter_t ecs_rule_iter( + const ecs_world_t *world, + const ecs_rule_t *rule) { - ecs_id_record_t *prev = elem->prev; - ecs_id_record_t *next = elem->next; - ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t it = {0}; + ecs_rule_iter_t *rit = &it.priv.iter.rule; + ecs_check(rule != NULL, ECS_INVALID_PARAMETER, 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; + 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.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; + 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; + 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 = 0; 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_insert_id_elem( +void flecs_rule_build_down_cache( ecs_world_t *world, - ecs_id_record_t *idr, - ecs_id_t wildcard, - ecs_id_record_t *widr) + 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_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); - if (!widr) { - widr = flecs_id_record_ensure(world, wildcard); + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); + if (!idr) { + return; } - 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); + ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + elem->entity = entity; + elem->idr = idr; - if (idr->flags & EcsIdTraversable) { - flecs_id_record_elem_insert(widr, idr, &idr->trav); + 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_record_t **records = table->data.records.array; + ecs_entity_t *entities = table->data.entities.array; + for (i = 0; i < count; i ++) { + ecs_record_t *r = records[i]; + if (r->row & EcsEntityIsTraversable) { + flecs_rule_build_down_cache( + world, a, ctx, cache, trav, entities[i]); + } + } } } } static -void flecs_remove_id_elem( - ecs_id_record_t *idr, - ecs_id_t wildcard) +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_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + ecs_id_t *ids = table->type.array; + int32_t i = tr->index, end = i + tr->count; + bool is_root = root_column == -1; - 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); + for (; i < end; i ++) { + ecs_entity_t second = ecs_pair_second(world, ids[i]); + if (is_root) { + root_column = i; + } - if (idr->flags & EcsIdTraversable) { - flecs_id_record_elem_remove(idr, &idr->trav); + 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); } } } -static -ecs_id_t flecs_id_record_hash( - ecs_id_t id) +void flecs_rule_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache) { - 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; + ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); } -static -ecs_id_record_t* flecs_id_record_new( - ecs_world_t *world, - ecs_id_t id) +void flecs_rule_get_down_cache( + 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, *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); + 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; } +} - ecs_table_cache_init(world, &idr->cache); - - idr->id = id; - idr->refcount = 1; - idr->reachable.current = -1; +void flecs_rule_get_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); - bool is_wildcard = ecs_id_is_wildcard(id); - bool is_pair = ECS_IS_PAIR(id); + 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_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); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + 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; + } - /* Relationship object can be 0, as tables without a ChildOf - * relationship are added to the (ChildOf, 0) id record */ - tgt = ECS_PAIR_SECOND(id); + ecs_id_t id = table->type.array[tr->index]; -#ifdef FLECS_DEBUG - /* Check constraints */ - if (tgt) { - tgt = ecs_get_alive(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; + 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; + } +} - /* 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; +/** + * @file addons/system/system.c + * @brief System addon. + */ - /* If pair is not a wildcard, append it to wildcard lists. These - * allow for quickly enumerating all relationships for an object, - * or all objecs 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); +#ifdef FLECS_SYSTEM - 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; - } +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) } +}; - /* 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. */ +/* -- Public API -- */ - /* 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); +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; - /* Add reference to (*, tgt) id record to entity record */ - tgt_r->idr = idr_t; - } + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; } - 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 (tick_source) { + const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); - 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); + 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; + } } - /* Update counters */ - world->info.id_create_total ++; - - if (!is_wildcard) { - world->info.id_count ++; + 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); + } - if (idr->type_info) { - world->info.component_id_count ++; - } else { - world->info.tag_id_count ++; - } + ecs_time_t time_start; + bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); + if (measure_time) { + ecs_os_get_time(&time_start); + } - if (is_pair) { - world->info.pair_id_count ++; - } + ecs_world_t *thread_ctx = world; + if (stage) { + thread_ctx = stage->thread_ctx; } else { - world->info.wildcard_id_count ++; + stage = &world->stages[0]; } - return idr; -#ifdef FLECS_DEBUG -error: - return NULL; -#endif -} + /* Prepare the query iterator */ + ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); + ecs_iter_t *it = &qit; -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); -} + 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; -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_defer_begin(world, stage); - flecs_id_record_assert_empty(idr); + if (offset || limit) { + pit = ecs_page_iter(it, offset, limit); + it = &pit; + } - /* 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 (stage_count > 1 && system_data->multi_threaded) { + wit = ecs_worker_iter(it, stage_index, stage_count); + it = &wit; + } - 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)); + 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 { - 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); - } + while (ecs_iter_next(it)) { + action(it); } - - ecs_log_pop_2(); } + } else { + action(&qit); + ecs_iter_fini(&qit); } - /* Update counters */ - world->info.id_delete_total ++; + if (measure_time) { + system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); + } - if (!ecs_id_is_wildcard(id)) { - world->info.id_count --; + system_data->invoke_count ++; - if (ECS_IS_PAIR(id)) { - world->info.pair_id_count --; - } + flecs_defer_end(world, stage); - if (idr->type_info) { - world->info.component_id_count --; - } else { - world->info.tag_id_count --; - } - } else { - world->info.wildcard_id_count --; - } + return it->interrupted_by; +} - /* 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); +/* -- Public API -- */ - 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 */ - } +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); +} - 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_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_id_record_t* flecs_id_record_ensure( +ecs_entity_t ecs_run( ecs_world_t *world, - ecs_id_t id) + ecs_entity_t system, + ecs_ftime_t delta_time, + void *param) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - idr = flecs_id_record_new(world, id); - } - return idr; + return ecs_run_w_filter(world, system, delta_time, 0, 0, param); } -ecs_id_record_t* flecs_id_record_get( +ecs_query_t* ecs_system_get_query( const ecs_world_t *world, - ecs_id_t id) + ecs_entity_t system) { - 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); + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->query; } else { - idr = &world->id_index_lo[hash]; - if (!idr->id) { - idr = NULL; - } + return NULL; } - - return idr; } -ecs_id_record_t* flecs_query_id_record_get( +void* ecs_system_get_ctx( const ecs_world_t *world, - ecs_id_t id) + ecs_entity_t system) { - 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; + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->ctx; + } else { + return NULL; + } } -void flecs_id_record_claim( - ecs_world_t *world, - ecs_id_record_t *idr) +void* ecs_system_get_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system) { - (void)world; - idr->refcount ++; + const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); + if (s) { + return s->binding_ctx; + } else { + return NULL; + } } -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); +/* System deinitialization */ +static +void flecs_system_fini(ecs_system_t *sys) { + if (sys->ctx_free) { + sys->ctx_free(sys->ctx); + } - if (!rc) { - flecs_id_record_free(world, idr); + if (sys->binding_ctx_free) { + sys->binding_ctx_free(sys->binding_ctx); } - return rc; + ecs_poly_free(sys, ecs_system_t); } -void flecs_id_record_release_tables( +static +void flecs_system_init_timer( ecs_world_t *world, - ecs_id_record_t *idr) + ecs_entity_t entity, + const ecs_system_desc_t *desc) { - /* 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))) { - /* Tables can hold claims on each other, so releasing a table can - * cause the next element to get invalidated. Claim the next table - * so that we can safely iterate. */ - ecs_table_t *next = NULL; - if (it.next) { - next = it.next->table; - flecs_table_claim(world, next); - } - - /* Release current table */ - flecs_table_release(world, tr->hdr.table); - - /* Check if the only thing keeping the next table alive is our - * claim. If so, move to the next record before releasing */ - if (next) { - if (next->_->refcount == 1) { - it.next = it.next->next; - } + 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); + } - flecs_table_release(world, next); + 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 } } -bool flecs_id_record_set_type_info( +ecs_entity_t ecs_system_init( ecs_world_t *world, - ecs_id_record_t *idr, - const ecs_type_info_t *ti) + const ecs_system_desc_t *desc) { - 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 --; - } - } + 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; - bool changed = idr->type_info != ti; - idr->type_info = ti; + ecs_query_desc_t query_desc = desc->query; + query_desc.filter.entity = entity; - return changed; -} + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, entity); + return 0; + } -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); - } + /* Prevent the system from moving while we're initializing */ + flecs_defer_begin(world, &world->stages[0]); - return map; -} + system->query = query; + system->query_entity = query->filter.entity; -ecs_hashmap_t* flecs_id_name_index_ensure( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); + system->run = desc->run; + system->action = desc->callback; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + system->ctx = desc->ctx; + system->binding_ctx = desc->binding_ctx; - return flecs_id_record_name_index_ensure(world, idr); -} + system->ctx_free = desc->ctx_free; + system->binding_ctx_free = desc->binding_ctx_free; -ecs_hashmap_t* flecs_id_name_index_get( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); + system->tick_source = desc->tick_source; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } + system->multi_threaded = desc->multi_threaded; + system->no_readonly = desc->no_readonly; - return idr->name_index; -} + flecs_system_init_timer(world, entity, desc); -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); + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]system#[reset] %s created", + ecs_get_name(world, entity)); + } - ecs_id_record_t* idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } + ecs_defer_end(world); + } else { + ecs_system_t *system = ecs_poly(poly->poly, ecs_system_t); - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} + if (desc->run) { + system->run = desc->run; + } + if (desc->callback) { + system->action = desc->callback; + } -const 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); -} + 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); + } + } -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)); + 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 flecs_fini_id_records( +void FlecsSystemImport( 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)); - } + ECS_MODULE(world, FlecsSystem); - 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_set_name_prefix(world, "Ecs"); - ecs_assert(ecs_map_count(&world->id_index_hi) == 0, - ECS_INTERNAL_ERROR, NULL); + flecs_bootstrap_tag(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); - ecs_map_fini(&world->id_index_hi); - ecs_os_free(world->id_index_lo); + /* 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/flecs-sys/flecs.h b/flecs-sys/flecs.h index 89e4def..8aebdfb 100644 --- a/flecs-sys/flecs.h +++ b/flecs-sys/flecs.h @@ -353,9 +353,9 @@ extern "C" { #define ECS_ID_ON_DELETE(flags) \ ((ecs_entity_t[]){0, EcsRemove, EcsDelete, 0, EcsPanic}\ [((flags) & EcsIdOnDeleteMask)]) -#define ECS_ID_ON_DELETE_OBJECT(flags) ECS_ID_ON_DELETE(flags >> 3) +#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_OBJECT_FLAG(id) (1u << (3 + ((id) - EcsRemove))) +#define ECS_ID_ON_DELETE_TARGET_FLAG(id) (1u << (3 + ((id) - EcsRemove))) //////////////////////////////////////////////////////////////////////////////// @@ -447,9 +447,10 @@ extern "C" { #define EcsQueryHasRefs (1u << 1u) /* Does query have references */ #define EcsQueryIsSubquery (1u << 2u) /* Is query a subquery */ #define EcsQueryIsOrphaned (1u << 3u) /* Is subquery orphaned */ -#define EcsQueryHasOutColumns (1u << 4u) /* Does query have out columns */ -#define EcsQueryHasMonitor (1u << 5u) /* Does query track changes */ -#define EcsQueryTrivialIter (1u << 6u) /* Does the query require special features to iterate */ +#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 */ //////////////////////////////////////////////////////////////////////////////// @@ -467,7 +468,7 @@ extern "C" { #endif -#if defined(_WIN32) || defined(_MSC_VER) || defined(__MING32__) +#if defined(_WIN32) || defined(_MSC_VER) #define ECS_TARGET_WINDOWS #elif defined(__ANDROID__) #define ECS_TARGET_ANDROID @@ -486,8 +487,8 @@ extern "C" { #define ECS_TARGET_POSIX #endif -#if defined(__MING32__) -#define ECS_TARGET_POSIX +#if defined(__MINGW32__) || defined(__MINGW64__) +#define ECS_TARGET_MINGW #endif #if defined(_MSC_VER) @@ -522,6 +523,59 @@ extern "C" { #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" +#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" +#endif + /* Standard library dependencies */ #include #include @@ -662,8 +716,27 @@ typedef struct ecs_allocator_t ecs_allocator_t; #define ECS_CAST(T, V) (static_cast(V)) #endif -#define ECS_CONCAT(a, b) a ## b +/* 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 @@ -708,10 +781,7 @@ typedef struct ecs_allocator_t ecs_allocator_t; //////////////////////////////////////////////////////////////////////////////// /** Translate C type to id. */ -#define ecs_id(T) FLECS__E##T - -/** Translate C type to system function. */ -#define ecs_iter_action(T) FLECS__F##T +#define ecs_id(T) FLECS_ID##T##ID_ //////////////////////////////////////////////////////////////////////////////// @@ -790,7 +860,7 @@ typedef struct ecs_allocator_t ecs_allocator_t; (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];\ + const type *src_var = &((const type*)_src_ptr)[i];\ (void)dst_var;\ (void)src_var;\ __VA_ARGS__\ @@ -855,7 +925,7 @@ typedef struct ecs_vec_t { void *array; int32_t count; int32_t size; -#ifdef FLECS_DEBUG +#ifdef FLECS_SANITIZE ecs_size_t elem_size; #endif } ecs_vec_t; @@ -925,7 +995,7 @@ void ecs_vec_remove_last( FLECS_API ecs_vec_t ecs_vec_copy( struct ecs_allocator_t *allocator, - ecs_vec_t *vec, + const ecs_vec_t *vec, ecs_size_t size); #define ecs_vec_copy_t(allocator, vec, T) \ @@ -1438,7 +1508,7 @@ ecs_map_val_t* ecs_map_get( /* Get element as pointer (auto-dereferences _ptr) */ FLECS_API -void* _ecs_map_get_deref( +void* ecs_map_get_deref_( const ecs_map_t *map, ecs_map_key_t key); @@ -1509,17 +1579,17 @@ void ecs_map_copy( 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_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, v)) +#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_CAST(T*, ecs_map_ensure_alloc(m, ECS_SIZEOF(T), k)) -#define ecs_map_remove_ptr(m, k) ((void*)(ecs_map_remove(m, 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_CAST(void*, ecs_map_value(it)) +#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 @@ -1716,6 +1786,13 @@ bool ecs_strbuf_appendflt( double v, char nan_delim); +/* Append boolean to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool 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 @@ -2183,7 +2260,7 @@ void ecs_os_set_api_defaults(void); #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_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))) @@ -2224,7 +2301,7 @@ void ecs_os_set_api_defaults(void); #define ecs_offset(ptr, T, index)\ ECS_CAST(T*, ECS_OFFSET(ptr, ECS_SIZEOF(T) * index)) -#if defined(ECS_TARGET_MSVC) +#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) @@ -2247,7 +2324,7 @@ void ecs_os_set_api_defaults(void); #endif /* Files */ -#if defined(ECS_TARGET_MSVC) +#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) @@ -2320,6 +2397,16 @@ void ecs_os_strset(char **str, const char *value); #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_() @@ -2411,59 +2498,171 @@ extern "C" { * @{ */ -/** An id. 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. */ +/** 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. */ +/** 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; -/** An array with (component) ids */ +/** 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. */ +/** 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 is where entities and components are stored */ +/** 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 */ +/** A term is a single element in a query. */ typedef struct ecs_term_t ecs_term_t; -/** A query allows for cached iteration over ECS data */ -typedef struct ecs_query_t ecs_query_t; - -/** A filter allows for uncached, ad hoc iteration over ECS data */ +/** 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 rule implements a non-trivial filter */ +/** 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 entiites, 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 reacts to events matching multiple filter terms */ +/** 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++): + * + * world.observer() + * .event(flecs::OnAdd) + * .each([](Position& p) { + * // called when Position is added to an entity + * }); + * + * 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 contains lists of triggers for specific events/components */ +/** 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; -/* An iterator lets an application iterate entities across tables. */ +/* 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; -/** Refs cache data that lets them access components faster than ecs_get. */ +/** 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 (callbacks) */ +/** 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 */ +/** 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; -/* Internal index that stores tables tables for a (component) id */ +/** 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; -/* Internal table storage record */ +/** 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. @@ -2703,14 +2902,19 @@ typedef enum ecs_oper_kind_t { #define EcsFilter (1u << 10) /**< Prevent observer from triggering on term */ #define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent) -/* Term flags discovered & set during filter creation. */ -#define EcsTermMatchAny (1 << 0) -#define EcsTermMatchAnySrc (1 << 1) -#define EcsTermSrcFirstEq (1 << 2) -#define EcsTermSrcSecondEq (1 << 3) -#define EcsTermTransitive (1 << 4) -#define EcsTermReflexive (1 << 5) -#define EcsTermIdInherited (1 << 6) +/* 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) + +/* 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 { @@ -2720,12 +2924,11 @@ typedef struct ecs_term_id_t { * To explicitly set the id to 0, leave the id * member to 0 and set EcsIsEntity in flags. */ - char *name; /**< Name. This can be either the variable name + const char *name; /**< Name. This can be either the variable name * (when the EcsIsVariable flag is set) or an - * entity name. Entity names are used to - * initialize the id member during term - * finalization and will be freed when term.move - * is set to true. */ + * 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 @@ -2759,7 +2962,7 @@ struct ecs_term_t { bool move; /**< Used by internals */ }; -/** Use this variable to initialize user-allocated filter object */ +/** Use $this variable to initialize user-allocated filter object */ FLECS_API extern ecs_filter_t ECS_FILTER_INIT; /** Filters alllow for ad-hoc quick filtering of entity tables. */ @@ -2909,9 +3112,6 @@ extern "C" { /** A stage enables modification while iterating and from multiple threads */ typedef struct ecs_stage_t ecs_stage_t; -/** A record stores data to map an entity id to a location in a table */ -typedef struct ecs_record_t ecs_record_t; - /** Table data */ typedef struct ecs_data_t ecs_data_t; @@ -2978,12 +3178,17 @@ struct ecs_ref_t { ecs_record_t *record; /* Entity index record */ }; -/* Cursor to stack allocator (used internally) */ +/* 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_page_t *cur; + 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 */ @@ -3099,7 +3304,7 @@ typedef struct ecs_rule_iter_t { /* 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_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; @@ -3183,6 +3388,7 @@ struct ecs_iter_t { /* 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 */ }; @@ -3228,7 +3434,7 @@ extern "C" { //////////////////////////////////////////////////////////////////////////////// /** This allows passing 0 as type to functions that accept ids */ -#define FLECS__E0 0 +#define FLECS_ID0ID_ 0 FLECS_API char* ecs_module_path_from_c( @@ -3265,6 +3471,10 @@ 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))) @@ -3331,7 +3541,7 @@ typedef struct { } flecs_hashmap_result_t; FLECS_DBG_API -void _flecs_hashmap_init( +void flecs_hashmap_init_( ecs_hashmap_t *hm, ecs_size_t key_size, ecs_size_t value_size, @@ -3340,34 +3550,34 @@ void _flecs_hashmap_init( 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_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( +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)) + (V*)flecs_hashmap_get_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) FLECS_DBG_API -flecs_hashmap_result_t _flecs_hashmap_ensure( +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_hashmap_ensure_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) FLECS_DBG_API -void _flecs_hashmap_set( +void flecs_hashmap_set_( ecs_hashmap_t *map, ecs_size_t key_size, void *key, @@ -3375,20 +3585,20 @@ void _flecs_hashmap_set( const void *value); #define flecs_hashmap_set(map, key, value)\ - _flecs_hashmap_set(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(*value), value) + flecs_hashmap_set_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(*value), value) FLECS_DBG_API -void _flecs_hashmap_remove( +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_hashmap_remove_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) FLECS_DBG_API -void _flecs_hashmap_remove_w_hash( +void flecs_hashmap_remove_w_hash_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, @@ -3396,7 +3606,7 @@ void _flecs_hashmap_remove_w_hash( 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_hashmap_remove_w_hash_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V), hash) FLECS_DBG_API ecs_hm_bucket_t* flecs_hashmap_get_bucket( @@ -3420,17 +3630,17 @@ flecs_hashmap_iter_t flecs_hashmap_iter( ecs_hashmap_t *map); FLECS_DBG_API -void* _flecs_hashmap_next( +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)) + (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)) + (V*)flecs_hashmap_next_(map, ECS_SIZEOF(K), key, ECS_SIZEOF(V)) #ifdef __cplusplus } @@ -3488,7 +3698,7 @@ typedef struct ecs_bulk_desc_t { int32_t _canary; ecs_entity_t *entities; /**< Entities to bulk insert. Entity ids provided by - * the application application must be empty (cannot + * the application must be empty (cannot * have components). If no entity ids are provided, the * operation will create 'count' new entities. */ @@ -3616,6 +3826,18 @@ typedef struct ecs_query_desc_t { * 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. @@ -3673,6 +3895,48 @@ typedef struct ecs_observer_desc_t { int32_t term_index; } ecs_observer_desc_t; +/** Used with ecs_emit. + * + * \ingroup observers + */ +typedef struct ecs_event_desc_t { + /** The event id. Only triggers for the specified event will be notified */ + ecs_entity_t event; + + /** Component ids. Only triggers 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. Assigned to iter param member */ + const void *param; + + /** Observable (usually the world) */ + ecs_poly_t *observable; + + /** Event flags */ + ecs_flags32_t flags; +} ecs_event_desc_t; + + /** * @defgroup misc_types Miscellaneous types * @brief Types used to create entities, observers, queries and more. @@ -3834,9 +4098,6 @@ extern "C" { * @{ */ -/** Bit added to flags to differentiate between id flags and generation */ -#define ECS_ID_FLAG_BIT (1ull << 63) - /** Indicates that the id is a pair. */ FLECS_API extern const ecs_id_t ECS_PAIR; @@ -3950,7 +4211,7 @@ FLECS_API extern const ecs_entity_t EcsExclusive; FLECS_API extern const ecs_entity_t EcsAcyclic; /** Marks a relationship as traversable. Traversable relationships may be - * traversed with "up" queries. Traversable relatinoships are acyclic. */ + * 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. @@ -3969,13 +4230,13 @@ FLECS_API extern const ecs_entity_t EcsWith; */ 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. */ +/** 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. */ +/** 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 */ @@ -4006,49 +4267,45 @@ FLECS_API extern const ecs_entity_t EcsModule; FLECS_API extern const ecs_entity_t EcsPrivate; /** Tag added to prefab entities. Any entity with this tag is automatically - * ignored by filters/queries, unless EcsPrefab is explicitly added. */ + * 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 all queries/filters */ +/** 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. Triggers when an id (component, tag, pair) is added to an entity */ +/** Event that triggers when an id is added to an entity */ FLECS_API extern const ecs_entity_t EcsOnAdd; -/** Event. Triggers when an id (component, tag, pair) is removed from an entity */ +/** Event that triggers when an id is removed from an entity */ FLECS_API extern const ecs_entity_t EcsOnRemove; -/** Event. Triggers when a component is set for an entity */ +/** Event that triggers when a component is set for an entity */ FLECS_API extern const ecs_entity_t EcsOnSet; -/** Event. Triggers when a component is unset for an entity */ +/** Event that triggers when a component is unset for an entity */ FLECS_API extern const ecs_entity_t EcsUnSet; -/** Event. Exactly-once observer for when an entity matches/unmatches a filter */ +/** Event that triggers observer when an entity starts/stops matching a query */ FLECS_API extern const ecs_entity_t EcsMonitor; -/** Event. Triggers when an entity is deleted. - * Also used as relationship for defining cleanup behavior, see: - * https://github.com/SanderMertens/flecs/blob/master/docs/Relationships.md#cleanup-properties - */ -FLECS_API extern const ecs_entity_t EcsOnDelete; - -/** Event. Triggers when a table is created. */ +/** Event that triggers when a table is created. */ FLECS_API extern const ecs_entity_t EcsOnTableCreate; -/** Event. Triggers when a table is deleted. */ +/** Event that triggers when a table is deleted. */ FLECS_API extern const ecs_entity_t EcsOnTableDelete; -/** Event. Triggers when a table becomes empty (doesn't emit on creation). */ +/** Event that triggers when a table becomes empty (doesn't emit on creation). */ FLECS_API extern const ecs_entity_t EcsOnTableEmpty; -/** Event. Triggers when a table becomes non-empty. */ +/** 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. For details see: - * https://github.com/SanderMertens/flecs/blob/master/docs/Relationships.md#cleanup-properties - */ + * 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 @@ -4063,7 +4320,11 @@ FLECS_API extern const ecs_entity_t EcsDelete; * 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, @@ -4109,9 +4370,15 @@ FLECS_API extern const ecs_entity_t EcsPhase; #define EcsFirstUserComponentId (8) /** The first user-defined entity starts from this id. Ids up to this number - * are reserved for builtin components */ + * 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 + */ + /** @} */ /** @} */ @@ -4127,18 +4394,17 @@ FLECS_API extern const ecs_entity_t EcsPhase; */ /** Create a new world. - * A world manages all the ECS data and supporting infrastructure. Applications - * must have at least one world. Entities, component and system handles are - * local to a world and should not be shared between worlds. - * - * This operation creates a world with all builtin modules loaded. + * 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); -/** Same as ecs_init, but with minimal set of modules loaded. +/** 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 */ @@ -4146,9 +4412,9 @@ FLECS_API ecs_world_t* ecs_mini(void); /** Create a new world with arguments. - * Same as ecs_init, but allows passing in command line arguments. These can be - * used to dynamically enable flecs features to an application. Currently these - * arguments are not used. + * 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 */ @@ -4168,6 +4434,8 @@ 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. @@ -4576,28 +4844,54 @@ bool ecs_stage_is_async( * * @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_context( +void ecs_set_ctx( ecs_world_t *world, - void *ctx); + 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_context. If no context was set, the + * @return The context set with ecs_set_ctx. If no context was set, the * function returns NULL. */ FLECS_API -void* ecs_get_context( +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. This pointer will remain valid for as long - * as the world is valid. + * @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( @@ -4749,12 +5043,12 @@ ecs_entity_t ecs_get_entity( * @return True if the pointer is of the specified type. */ FLECS_API -bool _ecs_poly_is( +bool ecs_poly_is_( const ecs_poly_t *object, int32_t type); #define ecs_poly_is(object, type)\ - _ecs_poly_is(object, type##_magic) + ecs_poly_is_(object, type##_magic) /** Make a pair id. * This function is equivalent to using the ecs_pair macro, and is added for @@ -4931,9 +5225,8 @@ ecs_entity_t ecs_clone( /** Delete an entity. * This operation will delete an entity and all of its components. The entity id - * will be recycled. Repeatedly calling ecs_delete without ecs_new or - * ecs_new_w_id will cause a memory leak as it will cause - * the list with ids that can be recycled to grow unbounded. + * 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. @@ -4965,7 +5258,7 @@ void ecs_delete_with( /** 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 has no side effects. + * already has the id, this operation will have no side effects. * * @param world The world. * @param entity The entity. @@ -4979,7 +5272,7 @@ void ecs_add_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 has no side effects. + * does not have the id, this operation will have no side effects. * * @param world The world. * @param entity The entity. @@ -5020,9 +5313,7 @@ void ecs_override_id( ecs_id_t id); /** Clear all components. - * This operation will clear all components from an entity but will not delete - * the entity itself. This effectively prevents the entity id from being - * recycled. + * This operation will remove all components from an entity. * * @param world The world. * @param entity The entity. @@ -5032,9 +5323,8 @@ void ecs_clear( ecs_world_t *world, ecs_entity_t entity); - -/** Remove all instances of the specified id. - * This will remove the specified id from all entities (tables). Teh id may be +/** 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. @@ -5209,7 +5499,24 @@ FLECS_API void* ecs_get_mut_id( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id); + ecs_id_t id); + +/** Combines get_mut + modifed 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 @@ -5348,13 +5655,12 @@ FLECS_API void* ecs_emplace_id( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id); + ecs_id_t id); /** Signal that a component has been modified. - * This operation allows an application to signal to Flecs that a component has - * been modified. As a result, OnSet systems will be invoked. - * - * This operation is commonly used together with ecs_get_mut. + * 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. @@ -5368,14 +5674,17 @@ void ecs_modified_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 and ecs_modified. + * 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 pointer to the value. + * @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. */ @@ -5396,10 +5705,10 @@ ecs_entity_t ecs_set_id( */ /** Test whether an entity is valid. - * Entities that are valid can be used with API functions. + * 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. If the provided id is - * a pair, the contents of the pair will be checked for validity. + * An entity is valid if it is not 0 and if it is alive. * * 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 @@ -5419,8 +5728,23 @@ bool ecs_is_valid( ecs_entity_t e); /** Test whether an entity is alive. - * An entity is alive when it has been returned by ecs_new (or similar) or if - * it is not empty (componentts have been explicitly added to the id). + * 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: + * + * 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 * * @param world The world. * @param e The entity. @@ -5623,8 +5947,9 @@ bool ecs_has_id( ecs_id_t id); /** Test if an entity owns an id. - * This operation returns true if the entity has the specified id. Other than - * ecs_has_id this operation will not return true if the id is inherited. + * 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. @@ -5639,8 +5964,8 @@ bool ecs_owns_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. + * 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. @@ -5696,7 +6021,7 @@ ecs_entity_t ecs_get_target_for_id( ecs_entity_t rel, ecs_id_t id); -/** Return depth for entity in tree for relationship rel. +/** 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. @@ -5911,12 +6236,19 @@ ecs_entity_t ecs_lookup_path_w_sep( * * 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 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 @@ -6117,6 +6449,20 @@ ecs_entity_t ecs_component_init( ecs_world_t *world, const ecs_component_desc_t *desc); +/** Get the type for an id. + * This function returnsthe 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 @@ -6202,20 +6548,6 @@ bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id); -/** Get the type for an id. - * This function returnsthe 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); - /** 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 @@ -6431,10 +6763,36 @@ 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); @@ -6553,24 +6911,28 @@ 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 +/** 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 + * 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. + * @return The index of the $this variable. */ FLECS_API int32_t ecs_filter_find_this_var( const ecs_filter_t *filter); -/** Convert ter, to string expression. +/** 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( @@ -6580,6 +6942,10 @@ char* ecs_term_str( /** 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( @@ -6651,18 +7017,29 @@ bool ecs_filter_next( /** 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. */ +/** 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. */ +/** 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, @@ -6731,6 +7108,7 @@ void ecs_query_fini( * 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( @@ -6783,6 +7161,9 @@ bool ecs_query_next( /** 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( @@ -6979,6 +7360,26 @@ 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); + /** @} */ /** @@ -6987,76 +7388,27 @@ int32_t ecs_query_entity_count( * @{ */ -typedef struct ecs_event_desc_t { - /** The event id. Only triggers for the specified event will be notified */ - ecs_entity_t event; - - /** Component ids. Only triggers 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. Assigned to iter param member */ - const void *param; - - /** Observable (usually the world) */ - ecs_poly_t *observable; - - /** Event flags */ - ecs_flags32_t flags; -} ecs_event_desc_t; - -/** 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. - * - * To send a notification for a single entity, an application should set the - * following members in the event descriptor: - * - * - table: set to the table of the entity - * - offset: set to the row of the entity in the table - * - count: set to 1 - * - * The table & row of the entity can be obtained like this: - * ecs_record_t *r = ecs_record_find(world, e); - * desc.table = r->table; - * desc.offset = ECS_RECORD_TO_ROW(r->row); - * - * @param world The world. - * @param desc Event parameters. - */ -FLECS_API -void ecs_emit( - ecs_world_t *world, - ecs_event_desc_t *desc); +/** 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); /** Create observer. * Observers are like triggers, but can subscribe for multiple terms. An @@ -7085,13 +7437,27 @@ 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_get_observer_ctx( +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_get_observer_binding_ctx( +void* ecs_observer_get_binding_ctx( const ecs_world_t *world, ecs_entity_t observer); @@ -7213,11 +7579,11 @@ ecs_entity_t ecs_iter_first( * // Rule that matches (Eats, *) * ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ * .terms = { - * { .first.id = Eats, .second.name = "_Food" } + * { .first.id = Eats, .second.name = "$food" } * } * }); * - * int food_var = ecs_rule_find_var(r, "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); @@ -7278,6 +7644,7 @@ void ecs_iter_set_var_as_range( * * @param it The iterator. * @param var_id The variable index. + * @return The variable value. */ FLECS_API ecs_entity_t ecs_iter_get_var( @@ -7295,6 +7662,7 @@ ecs_entity_t ecs_iter_get_var( * * @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( @@ -7312,13 +7680,13 @@ ecs_table_t* ecs_iter_get_var_as_table( * * @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. @@ -7335,6 +7703,21 @@ 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. @@ -7483,7 +7866,7 @@ ecs_id_t ecs_field_id( /** Return index of matched table column. * This function only returns column indices for fields that have been matched - * on the the $this variable. Fields matched on other tables will return -1. + * 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. @@ -7507,8 +7890,7 @@ ecs_entity_t ecs_field_src( int32_t index); /** Return field type size. - * Return type size of the data returned by field. Returns 0 if field has no - * data. + * 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. @@ -7537,22 +7919,6 @@ bool ecs_field_is_self( const ecs_iter_t *it, int32_t index); -/** 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); - - /** @} */ /** @@ -7562,6 +7928,7 @@ char* ecs_iter_str( */ /** 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. @@ -7570,59 +7937,88 @@ FLECS_API const ecs_type_t* ecs_table_get_type( const ecs_table_t *table); -/** Get column from table. - * This operation returns the component array for the provided index. +/** 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 index The index of the column (corresponds with element in type). - * @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. + * @param id The id. + * @return The index of the id in the table type, or -1 if not found. */ FLECS_API -void* ecs_table_get_column( +int32_t ecs_table_get_type_index( + const ecs_world_t *world, const ecs_table_t *table, - int32_t index, - int32_t offset); + ecs_id_t id); -/** Get column size from table. - * This operation returns the component size for the provided index. +/** 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 index The index of the column (corresponds with element in type). - * @return The component size, or 0 if the index is not a component. + * @param id The component id. + * @return The column index of the id, or -1 if not found/not a component. */ FLECS_API -size_t ecs_table_get_column_size( +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); -/** Get column index for id. - * This operation returns the index for an id in the table's type. +/** 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 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. + * @param index The column index. + * @return The index in the table type. */ FLECS_API -int32_t ecs_table_get_index( - const ecs_world_t *world, +int32_t ecs_table_column_to_type_index( const ecs_table_t *table, - ecs_id_t id); + int32_t index); -/** Test if table has id. - * Same as ecs_table_get_index(world, table, id) != -1. +/** Get column from table by column index. + * This operation returns the component array for the provided index. * - * @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. + * @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 -bool ecs_table_has_id( - const ecs_world_t *world, +void* ecs_table_get_column( const ecs_table_t *table, - ecs_id_t id); + int32_t index, + int32_t offset); /** Get column from table by component id. * This operation returns the component array for the provided component id. @@ -7639,40 +8035,15 @@ void* ecs_table_get_id( ecs_id_t id, int32_t offset); -/** 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. +/** Get column size from table. + * This operation returns the component size for the provided index. * - * @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 storage type for table. - * * @param table The table. - * @return The storage type of the table (components only). + * @param index The column index. + * @return The component size, or 0 if the index is not a component. */ FLECS_API -ecs_table_t* ecs_table_get_storage_table( - const ecs_table_t *table); - -/** Convert index in table type to index in table storage type. */ -FLECS_API -int32_t ecs_table_type_to_storage_index( - const ecs_table_t *table, - int32_t index); - -/** Convert index in table storage type to index in table type. */ -FLECS_API -int32_t ecs_table_storage_to_type_index( +size_t ecs_table_get_column_size( const ecs_table_t *table, int32_t index); @@ -7688,6 +8059,36 @@ 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. @@ -7765,16 +8166,19 @@ void ecs_table_unlock( ecs_world_t *world, ecs_table_t *table); -/** Returns whether table is a module or contains module contents - * Returns true for tables that have module contents. Can be used to filter out - * tables that do not contain application data. +/** 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. - * @return true if table contains module contents, false if not. + * @param flags The flags to test for. + * @return Whether the specified flags are set for the table. */ FLECS_API -bool ecs_table_has_module( - ecs_table_t *table); +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. @@ -8179,7 +8583,7 @@ int ecs_value_move_ctor( ecs_assert(id_ != 0, ECS_INVALID_PARAMETER, NULL); \ } \ (void)id_; \ - (void)ecs_id(id_); + (void)ecs_id(id_) /** Declare & define an entity. * @@ -8189,7 +8593,7 @@ int ecs_value_move_ctor( #define ECS_ENTITY(world, id, ...) \ ecs_entity_t ecs_id(id); \ ecs_entity_t id = 0; \ - ECS_ENTITY_DEFINE(world, id, __VA_ARGS__); + ECS_ENTITY_DEFINE(world, id, __VA_ARGS__) /** Forward declare a tag. */ #define ECS_TAG_DECLARE ECS_DECLARE @@ -8245,8 +8649,8 @@ int ecs_value_move_ctor( 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);\ - } + }\ + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL) /** Declare & define a component. * @@ -8290,7 +8694,7 @@ int ecs_value_move_ctor( ECS_OBSERVER_DEFINE(world, id, kind, __VA_ARGS__);\ ecs_entity_t id = ecs_id(id);\ (void)ecs_id(id);\ - (void)id; + (void)id /** Shorthand for creating an entity with ecs_entity_init. * @@ -8544,6 +8948,9 @@ int ecs_value_move_ctor( #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__) @@ -9104,7 +9511,7 @@ extern "C" { //////////////////////////////////////////////////////////////////////////////// FLECS_API -void _ecs_deprecated( +void ecs_deprecated_( const char *file, int32_t line, const char *msg); @@ -9116,7 +9523,7 @@ void _ecs_deprecated( * @param level The log level. */ FLECS_API -void _ecs_log_push(int32_t level); +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 @@ -9125,7 +9532,7 @@ void _ecs_log_push(int32_t level); * @param level The log level. */ FLECS_API -void _ecs_log_pop(int32_t level); +void ecs_log_pop_(int32_t level); /** Should current level be logged. * This operation returns true when the specified log level should be logged @@ -9152,13 +9559,13 @@ const char* ecs_strerror( //// Dummy macros for when logging is disabled //////////////////////////////////////////////////////////////////////////////// -#define _ecs_deprecated(file, line, msg)\ +#define ecs_deprecated_(file, line, msg)\ (void)file;\ (void)line;\ (void)msg -#define _ecs_log_push(level) -#define _ecs_log_pop(level) +#define ecs_log_push_(level) +#define ecs_log_pop_(level) #define ecs_should_log(level) false #define ecs_strerror(error_code)\ @@ -9172,7 +9579,7 @@ const char* ecs_strerror( //////////////////////////////////////////////////////////////////////////////// FLECS_API -void _ecs_print( +void ecs_print_( int32_t level, const char *file, int32_t line, @@ -9180,7 +9587,7 @@ void _ecs_print( ...); FLECS_API -void _ecs_printv( +void ecs_printv_( int level, const char *file, int32_t line, @@ -9188,7 +9595,7 @@ void _ecs_printv( va_list args); FLECS_API -void _ecs_log( +void ecs_log_( int32_t level, const char *file, int32_t line, @@ -9196,7 +9603,7 @@ void _ecs_log( ...); FLECS_API -void _ecs_logv( +void ecs_logv_( int level, const char *file, int32_t line, @@ -9204,7 +9611,7 @@ void _ecs_logv( va_list args); FLECS_API -void _ecs_abort( +void ecs_abort_( int32_t error_code, const char *file, int32_t line, @@ -9212,7 +9619,7 @@ void _ecs_abort( ...); FLECS_API -bool _ecs_assert( +bool ecs_assert_( bool condition, int32_t error_code, const char *condition_str, @@ -9222,7 +9629,7 @@ bool _ecs_assert( ...); FLECS_API -void _ecs_parser_error( +void ecs_parser_error_( const char *name, const char *expr, int64_t column, @@ -9230,7 +9637,7 @@ void _ecs_parser_error( ...); FLECS_API -void _ecs_parser_errorv( +void ecs_parser_errorv_( const char *name, const char *expr, int64_t column, @@ -9246,37 +9653,37 @@ void _ecs_parser_errorv( /* Base logging function. Accepts a custom level */ #define ecs_print(level, ...)\ - _ecs_print(level, __FILE__, __LINE__, __VA_ARGS__) + ecs_print_(level, __FILE__, __LINE__, __VA_ARGS__) #define ecs_printv(level, fmt, args)\ - _ecs_printv(level, __FILE__, __LINE__, fmt, args) + ecs_printv_(level, __FILE__, __LINE__, fmt, args) #define ecs_log(level, ...)\ - _ecs_log(level, __FILE__, __LINE__, __VA_ARGS__) + ecs_log_(level, __FILE__, __LINE__, __VA_ARGS__) #define ecs_logv(level, fmt, args)\ - _ecs_logv(level, __FILE__, __LINE__, 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__) +#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__) +#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__) +#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__) +#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__) + ecs_deprecated_(__FILE__, __LINE__, __VA_ARGS__) #else #define ecs_deprecated(...) #endif // FLECS_NO_DEPRECATED_WARNINGS @@ -9299,13 +9706,13 @@ void _ecs_parser_errorv( #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_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_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) @@ -9320,12 +9727,12 @@ void _ecs_parser_errorv( #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_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_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) @@ -9340,11 +9747,11 @@ void _ecs_parser_errorv( #define ecs_dbg_2(...) #define ecs_dbg_3(...) -#define ecs_log_push_1() _ecs_log_push(1); +#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_1() ecs_log_pop_(1); #define ecs_log_pop_2() #define ecs_log_pop_3() @@ -9392,13 +9799,13 @@ void _ecs_parser_errorv( #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) +#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_abort_(error_code, __FILE__, __LINE__, __VA_ARGS__);\ ecs_os_abort(); abort(); /* satisfy compiler/static analyzers */ /** Assert. @@ -9407,7 +9814,7 @@ void _ecs_parser_errorv( #define ecs_assert(condition, error_code, ...) #else #define ecs_assert(condition, error_code, ...)\ - if (!_ecs_assert(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__)) {\ + if (!ecs_assert_(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__)) {\ ecs_os_abort();\ }\ assert(condition) /* satisfy compiler/static analyzers */ @@ -9425,6 +9832,15 @@ void _ecs_parser_errorv( #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)) {\ @@ -9438,7 +9854,7 @@ void _ecs_parser_errorv( #else #ifdef FLECS_SOFT_ASSERT #define ecs_check(condition, error_code, ...)\ - if (!_ecs_assert(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__)) {\ + if (!ecs_assert_(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__)) {\ goto error;\ } #else // FLECS_SOFT_ASSERT @@ -9455,7 +9871,7 @@ void _ecs_parser_errorv( #else #ifdef FLECS_SOFT_ASSERT #define ecs_throw(error_code, ...)\ - _ecs_abort(error_code, __FILE__, __LINE__, __VA_ARGS__);\ + ecs_abort_(error_code, __FILE__, __LINE__, __VA_ARGS__);\ goto error; #else #define ecs_throw(error_code, ...)\ @@ -9466,10 +9882,10 @@ void _ecs_parser_errorv( /** Parser error */ #define ecs_parser_error(name, expr, column, ...)\ - _ecs_parser_error(name, expr, column, __VA_ARGS__) + 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) + ecs_parser_errorv_(name, expr, column, fmt, args) #endif // FLECS_LEGACY @@ -9572,6 +9988,7 @@ int ecs_log_last_error(void); #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) @@ -10192,7 +10609,7 @@ typedef struct EcsRateFilter { * * The timer is synchronous, and is incremented each frame by delta_time. * - * The tick_source entity will be be a tick source after this operation. Tick + * 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. @@ -10218,7 +10635,7 @@ ecs_entity_t ecs_set_timeout( * * The timer is synchronous, and is incremented each frame by delta_time. * - * The tick_source entity will be be a tick source after this operation. Tick + * 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. @@ -10239,7 +10656,7 @@ ecs_ftime_t ecs_get_timeout( * * The timer is synchronous, and is incremented each frame by delta_time. * - * The tick_source entity will be be a tick source after this operation. Tick + * 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. @@ -10269,10 +10686,7 @@ ecs_ftime_t ecs_get_interval( ecs_entity_t tick_source); /** Start timer. - * This operation resets the timer and starts it with the specified timeout. The - * entity must have the EcsTimer component (added by ecs_set_timeout and - * ecs_set_interval). If the entity does not have the EcsTimer component this - * operation will assert. + * This operation resets the timer and starts it with the specified timeout. * * @param world The world. * @param tick_source The timer to start. @@ -10283,8 +10697,7 @@ void ecs_start_timer( ecs_entity_t tick_source); /** Stop timer - * This operation stops a timer from triggering. The entity must have the - * EcsTimer component or this operation will assert. + * This operation stops a timer from triggering. * * @param world The world. * @param tick_source The timer to stop. @@ -10294,6 +10707,27 @@ 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. + * Intializes 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, @@ -10313,7 +10747,7 @@ void ecs_stop_timer( * 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 be a tick source after this operation. Tick + * 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. @@ -10546,9 +10980,6 @@ void ecs_reset_clock( * default pipeline (either the builtin pipeline or the pipeline set with * set_pipeline()). An application may run additional pipelines. * - * Note: calling this function from an application currently only works in - * single threaded applications with a single stage. - * * @param world The world. * @param pipeline The pipeline to run. */ @@ -10745,7 +11176,7 @@ ecs_entity_t ecs_system_init( desc.callback = id_; \ ecs_id(id_) = ecs_system_init(world, &desc); \ } \ - ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL) /** Declare & define a system. * @@ -10756,7 +11187,7 @@ ecs_entity_t ecs_system_init( 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; + (void)id /** Shorthand for creating a system with ecs_system_init. * @@ -10765,7 +11196,7 @@ ecs_entity_t ecs_system_init( * .entity = ecs_entity(world, { * .name = "MyEntity", * .add = { ecs_dependson(EcsOnUpdate) } - * }, + * }), * .query.filter.terms = { * { ecs_id(Position) }, * { ecs_id(Velocity) } @@ -10795,7 +11226,7 @@ ecs_entity_t ecs_system_init( * Any system may interrupt execution by setting the interrupted_by member in * the ecs_iter_t value. This is particularly useful for manual systems, where * the value of interrupted_by is returned by this operation. This, in - * cominbation with the param argument lets applications use manual systems + * 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. * @@ -10883,7 +11314,7 @@ ecs_query_t* ecs_system_get_query( * @return The context. */ FLECS_API -void* ecs_get_system_ctx( +void* ecs_system_get_ctx( const ecs_world_t *world, ecs_entity_t system); @@ -10897,7 +11328,7 @@ void* ecs_get_system_ctx( * @return The context. */ FLECS_API -void* ecs_get_system_binding_ctx( +void* ecs_system_get_binding_ctx( const ecs_world_t *world, ecs_entity_t system); @@ -11115,8 +11546,6 @@ 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 */ - ecs_metric_t active; /**< Whether system is active (is matched with >0 entities) */ - ecs_metric_t enabled; /**< Whether system is enabled */ int64_t last_; bool task; /**< Is system a task */ @@ -11124,6 +11553,18 @@ typedef struct ecs_system_stats_t { 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} */ @@ -11132,6 +11573,9 @@ typedef struct ecs_pipeline_stats_t { /** Vector with system ids of all systems in the pipeline. The systems are * stored in the order they are executed. Merges are represented by a 0. */ ecs_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. */ @@ -11409,7 +11853,7 @@ FLECS_API extern ECS_TAG_DECLARE(EcsCounterId); FLECS_API extern ECS_TAG_DECLARE(EcsGauge); /** Tag added to metric instances */ -FLECS_API extern ECS_COMPONENT_DECLARE(EcsMetricInstance); +FLECS_API extern ECS_TAG_DECLARE(EcsMetricInstance); /** Component with metric instance value */ FLECS_API extern ECS_COMPONENT_DECLARE(EcsMetricValue); @@ -11428,27 +11872,32 @@ typedef struct EcsMetricSource { typedef struct ecs_metric_desc_t { int32_t _canary; - /* Entity associated with metric */ + /** Entity associated with metric */ ecs_entity_t entity; - /* Entity associated with member that stores metric value. Must not be set + /** 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; - /* Tracks whether entities have the specified component id. Must not be set + /* 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, + /** 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 */ + /** Must be EcsGauge, EcsCounter, EcsCounterIncrement or EcsCounterId */ ecs_entity_t kind; - /* Description of metric. Will only be set if FLECS_DOC addon is enabled */ + /** Description of metric. Will only be set if FLECS_DOC addon is enabled */ const char *brief; } ecs_metric_desc_t; @@ -11562,6 +12011,8 @@ void FlecsMetricsImport( extern "C" { #endif +#define ECS_ALERT_MAX_SEVERITY_FILTERS (4) + /* Module id */ FLECS_API extern ECS_COMPONENT_DECLARE(FlecsAlerts); @@ -11571,6 +12022,7 @@ FLECS_API extern ECS_COMPONENT_DECLARE(FlecsAlerts); 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); @@ -11585,21 +12037,32 @@ typedef struct 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 */ + /** Entity associated with alert */ ecs_entity_t entity; - /* Alert query. An alert will be created for each entity that matches the + /** 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 + /** 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. * @@ -11609,12 +12072,40 @@ typedef struct ecs_alert_desc_t { */ const char *message; - /* Description of metric. Will only be set if FLECS_DOC addon is enabled */ + /** 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 + /** 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. @@ -11704,7 +12195,7 @@ void FlecsAlertsImport( #error "FLECS_NO_MONITOR failed: MONITOR is required by other addons" #endif /** - * @file addons/doc.h + * @file addons/monitor.h * @brief Doc module. * * The monitor module automatically tracks statistics from the stats addon and @@ -11738,6 +12229,7 @@ extern "C" { 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; @@ -11761,6 +12253,21 @@ typedef struct { 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( @@ -12057,9 +12564,11 @@ extern "C" { /** 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) */ + 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, @@ -12216,11 +12725,11 @@ int ecs_type_info_to_json_buf( /** Used with ecs_iter_to_json. */ typedef struct ecs_entity_to_json_desc_t { bool serialize_path; /**< Serialize full pathname */ - bool serialize_meta_ids; /**< Serialize 'meta' ids (Name, ChildOf, etc) */ 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 */ @@ -12228,10 +12737,13 @@ typedef struct ecs_entity_to_json_desc_t { 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, false, false, true, false, false, false, false, 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 @@ -12266,12 +12778,15 @@ int ecs_entity_to_json_buf( /** Used with ecs_iter_to_json. */ typedef struct ecs_iter_to_json_desc_t { - bool serialize_term_ids; /**< Serialize term (query) component ids */ + 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 */ @@ -12286,7 +12801,9 @@ typedef struct 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, \ @@ -12334,8 +12851,8 @@ int ecs_iter_to_json_buf( /** 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 */ + bool serialize_builtin; /**< Exclude flecs modules & contents */ + bool serialize_modules; /**< Exclude modules & contents */ } ecs_world_to_json_desc_t; /** Serialize world into JSON string. @@ -12853,6 +13370,7 @@ 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); @@ -12881,7 +13399,7 @@ 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); -/** Type kinds supported by reflection type system */ +/** Type kinds supported by meta addon */ typedef enum ecs_type_kind_t { EcsPrimitiveType, EcsBitmaskType, @@ -12898,10 +13416,9 @@ 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 */ - ecs_size_t size; /**< Computed size */ - ecs_size_t alignment; /**< Computed alignment */ } EcsMetaType; +/** Primitive type kinds supported by meta addon */ typedef enum ecs_primitive_kind_t { EcsBool = 1, EcsChar, @@ -12923,10 +13440,12 @@ typedef enum ecs_primitive_kind_t { EcsPrimitiveKindLast = EcsEntity } 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; @@ -12934,6 +13453,19 @@ typedef struct EcsMember { 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 */ @@ -12948,11 +13480,25 @@ typedef struct ecs_member_t { * 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 */ @@ -12969,6 +13515,7 @@ typedef struct ecs_enum_constant_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 */ @@ -12985,18 +13532,21 @@ typedef struct ecs_bitmask_constant_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; - int32_t count; + 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; + ecs_entity_t type; /**< Element type */ } EcsVector; @@ -13009,13 +13559,13 @@ 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 */ + 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 char *member); /**< Member name */ const ecs_world_t *world; void *ctx; @@ -13206,8 +13756,8 @@ typedef struct ecs_meta_type_op_t { 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; - ecs_entity_t unit; + 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; @@ -13231,7 +13781,7 @@ typedef struct ecs_meta_scope_t { 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_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? */ @@ -13315,6 +13865,11 @@ 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. */ @@ -14972,7 +15527,7 @@ ecs_entity_t ecs_module_init( #define ECS_MODULE(world, id)\ ecs_entity_t ecs_id(id) = 0; ECS_MODULE_DEFINE(world, id)\ - (void)ecs_id(id); + (void)ecs_id(id) /** Wrapper around ecs_import. * This macro provides a convenient way to load a module with the world. It can @@ -14980,7 +15535,7 @@ ecs_entity_t ecs_module_init( * * ECS_IMPORT(world, FlecsSystemsPhysics); */ -#define ECS_IMPORT(world, id) ecs_import_c(world, id##Import, #id); +#define ECS_IMPORT(world, id) ecs_import_c(world, id##Import, #id) #ifdef __cplusplus } @@ -15118,6 +15673,13 @@ 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 @@ -15679,7 +16241,7 @@ struct string { return m_const_str; } - std::size_t length() { + std::size_t length() const { return static_cast(m_length); } @@ -15688,7 +16250,7 @@ struct string { return N - 1; } - std::size_t size() { + std::size_t size() const { return length(); } @@ -15791,16 +16353,38 @@ struct enum_last { namespace _ { -#ifdef ECS_TARGET_MSVC -#define ECS_SIZE_T_STR "unsigned __int64" -#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" +#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 -#define ECS_SIZE_T_STR "constexpr size_t; size_t = long unsigned int" -#endif + #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 @@ -16362,7 +16946,7 @@ namespace flecs { /** * @defgroup cpp_core_filters Filters - * @brief Filters are are cheaper to create, but slower to iterate than flecs::query. + * @brief Filters are cheaper to create, but slower to iterate than flecs::query. * * \ingroup cpp_core * @{ @@ -16883,6 +17467,7 @@ using Primitive = EcsPrimitive; using Enum = EcsEnum; using Bitmask = EcsBitmask; using Member = EcsMember; +using MemberRanges = EcsMemberRanges; using Struct = EcsStruct; using Array = EcsArray; using Vector = EcsVector; @@ -17733,6 +18318,11 @@ struct metric_builder { 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; @@ -17913,7 +18503,8 @@ struct app_builder { { const ecs_world_info_t *stats = ecs_get_world_info(world); m_desc.target_fps = stats->target_fps; - if (m_desc.target_fps == static_cast(0.0)) { + ecs_ftime_t t_zero = 0.0; + if (ECS_EQ(m_desc.target_fps, t_zero)) { m_desc.target_fps = 60; } } @@ -18687,10 +19278,15 @@ 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); - T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); - dst = FLECS_MOV(value); + 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); + 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 @@ -18698,10 +19294,15 @@ 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); - T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); - dst = value; + 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); + 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 @@ -18709,11 +19310,15 @@ 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); - T& dst = *static_cast*>(ecs_get_mut_id(world, entity, id)); + if (!ecs_is_deferred(world)) { + T& dst = *static_cast*>(ecs_get_mut_id(world, entity, id)); + dst = FLECS_MOV(value); - dst = FLECS_MOV(value); - - ecs_modified_id(world, entity, id); + 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 @@ -18721,10 +19326,15 @@ 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); - T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); - dst = value; + 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); + 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...) @@ -18854,27 +19464,6 @@ struct world { return m_world; } - /** Get last delta_time. - */ - ecs_ftime_t delta_time() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->delta_time; - } - - /** Get current tick. - */ - int64_t tick() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->frame_count_total; - } - - /** Get current simulation time. - */ - ecs_ftime_t time() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->world_time_total; - } - /** Signal application should quit. * After calling this operation, the next call to progress() returns false. */ @@ -19134,16 +19723,34 @@ struct world { * * @param ctx The world context. */ - void set_context(void* ctx) const { - ecs_set_context(m_world, ctx); + 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_context() const { - return ecs_get_context(m_world); + 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. @@ -19199,6 +19806,7 @@ struct world { 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); @@ -19207,8 +19815,10 @@ struct world { /** 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) const; + flecs::entity lookup(const char *name, bool search_path = true) const; /** Set singleton component. */ @@ -19360,7 +19970,7 @@ struct world { template void remove() const; - /** Adds a pair to the singleton component. + /** Removes the pair singleton component. * * @tparam First The first element of the pair * @tparam Second The second element of the pair @@ -19368,7 +19978,7 @@ struct world { template void remove() const; - /** Adds a pair to the singleton component. + /** Removes the pair singleton component. * * @tparam First The first element of the pair * @param second The second element of the pair. @@ -19376,7 +19986,7 @@ struct world { template void remove(flecs::entity_t second) const; - /** Adds a pair to the singleton entity. + /** Removes the pair singleton component. * * @param first The first element of the pair * @param second The second element of the pair @@ -19395,6 +20005,38 @@ struct world { 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. @@ -19529,6 +20171,8 @@ struct world { 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); @@ -19648,6 +20292,18 @@ struct world { 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. @@ -19728,7 +20384,7 @@ flecs::entity entity(Args &&... args) const; * \ingroup cpp_entities */ template ::value > = 0> -flecs::entity id(E value) const; +flecs::id id(E value) const; /** Convert enum constant to entity. * @@ -20031,31 +20687,23 @@ bool progress(ecs_ftime_t delta_time = 0.0) const; */ 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; -/** Get timescale. - * @see ecs_get_time_scale - */ -ecs_ftime_t get_time_scale() const; - -/** Get tick. - * @return Monotonically increasing frame count. - */ -int64_t get_tick() const; - /** Set target FPS. * @see ecs_set_target_fps */ void set_target_fps(ecs_ftime_t target_fps) const; -/** Get target FPS. - * @return Configured frames per second. - */ -ecs_ftime_t get_target_fps() const; - /** Reset simulation clock. * @see ecs_reset_clock */ @@ -20149,6 +20797,11 @@ flecs::system_builder system(Args &&... args) const; 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 /** @@ -20312,7 +20965,6 @@ flecs::string to_json() { * \memberof flecs::world * \ingroup cpp_addons_json */ -template 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); } @@ -20642,11 +21294,6 @@ struct iter { flecs::table_range range() const; - /** Is current type a module or does it contain module contents? */ - bool has_module() const { - return ecs_table_has_module(m_iter->table); - } - /** Access ctx. * ctx contains the context pointer assigned to a system. */ @@ -20732,7 +21379,7 @@ struct iter { * * @param index The field index. */ - flecs::entity id(int32_t index) const; + flecs::id id(int32_t index) const; /** Obtain pair id matched for field. * This operation will fail if the id is not a pair. @@ -20797,6 +21444,14 @@ struct iter { 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; @@ -20895,6 +21550,7 @@ struct iter { } // namespace flecs /** @} */ + /** * @file addons/cpp/entity.hpp * @brief Entity class. @@ -20957,7 +21613,7 @@ struct entity_view : public id { return m_id; } - /** Check is entity is valid. + /** Check if entity is valid. * * @return True if the entity is alive, false otherwise. */ @@ -20969,7 +21625,7 @@ struct entity_view : public id { return is_valid(); } - /** Check is entity is alive. + /** Check if entity is alive. * * @return True if the entity is alive, false otherwise. */ @@ -21385,9 +22041,10 @@ struct entity_view : public id { * 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) const; + flecs::entity lookup(const char *path, bool search_path = false) const; /** Check if entity has the provided entity. * @@ -21753,6 +22410,23 @@ struct entity_builder : entity_view { 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. * @@ -21999,6 +22673,17 @@ struct entity_builder : entity_view { 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. @@ -22031,7 +22716,7 @@ struct entity_builder : entity_view { } /** Remove a pair. - * This operation adds a pair to the entity. + * This operation removes the pair from the entity. * * @tparam First The first element of the pair * @param second The second element of the pair. @@ -22053,7 +22738,7 @@ struct entity_builder : entity_view { } /** Remove a pair. - * This operation adds a pair to the entity. + * This operation removes the pair from the entity. * * @tparam First The first element of the pair * @param constant the enum constant. @@ -22225,34 +22910,6 @@ struct entity_builder : entity_view { 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); - } - - /** 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); - } - /** Enable an entity. * Enabled entities are matched with systems and can be searched with * queries. @@ -22290,7 +22947,7 @@ struct entity_builder : entity_view { */ template Self& enable() { - return this->enable(_::cpp_type::id()); + return this->enable(_::cpp_type::id(this->m_world)); } /** Enable a pair. @@ -22605,6 +23262,7 @@ struct entity_builder : entity_view { } /** Entities created in function will have the current entity. + * This operation is thread safe. * * @param func The function to call. */ @@ -22629,6 +23287,7 @@ struct entity_builder : entity_view { } /** 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. @@ -22651,6 +23310,11 @@ struct entity_builder : entity_view { 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) { @@ -22821,6 +23485,114 @@ Self& quantity() { # 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 + + protected: Self& to_base() { return *static_cast(this); @@ -22942,7 +23714,7 @@ struct entity : entity_builder _::cpp_type::id(m_world)))); } - /** Get mutable pointer for a pair. + /** 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. @@ -23043,16 +23815,12 @@ struct entity : entity_builder */ template ref get_ref() const { - // Ensure component is registered - _::cpp_type::id(m_world); - return ref(m_world, m_id); + return ref(m_world, m_id, _::cpp_type::id(m_world)); } template , typename A = actual_type_t

> ref get_ref() const { - // Ensure component is registered - _::cpp_type::id(m_world); return ref(m_world, m_id, ecs_pair(_::cpp_type::id(m_world), _::cpp_type::id(m_world))); @@ -23060,16 +23828,12 @@ struct entity : entity_builder template ref get_ref(flecs::entity_t second) const { - // Ensure component is registered - _::cpp_type::id(m_world); return ref(m_world, m_id, ecs_pair(_::cpp_type::id(m_world), second)); } template ref get_ref_second(flecs::entity_t first) const { - // Ensure component is registered - _::cpp_type::id(m_world); return ref(m_world, m_id, ecs_pair(first, _::cpp_type::id(m_world))); } @@ -23139,7 +23903,6 @@ const char* from_json(const char *json) { return ecs_entity_from_json(m_world, m_id, json, nullptr); } - # endif }; @@ -23349,44 +24112,177 @@ struct each_invoker : public invoker { term_ptrs terms; if (terms.populate(iter)) { - invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + 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); + } + + // 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 invokers 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_invoker : public invoker { + // 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_invoker(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + explicit find_invoker(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, 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 { - invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); + return 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); - } - - // 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 invokers always use instanced iterators + // Find invokers always use instanced iterators static bool instanced() { return true; } @@ -23397,24 +24293,31 @@ struct each_invoker : public invoker { template class ColumnType, typename... Args, if_t< sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> - static void invoke_callback( + 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 each() without flecs::entity argument"); + "no entities returned, use find() without flecs::entity argument"); for (size_t i = 0; i < count; i ++) { - func(flecs::entity(world, iter->entities[i]), + if (func(flecs::entity(world, iter->entities[i]), (ColumnType< remove_reference_t >(comps, i) - .get_row())...); + .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 @@ -23422,7 +24325,7 @@ struct each_invoker : public invoker { template class ColumnType, typename... Args, int Enabled = PassIter, if_t< sizeof...(Components) == sizeof...(Args) && Enabled> = 0> - static void invoke_callback( + static flecs::entity invoke_callback( ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) { size_t count = static_cast(iter->count); @@ -23433,22 +24336,29 @@ struct each_invoker : public invoker { } flecs::iter it(iter); + flecs::entity result; 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())...); + 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 void invoke_callback( + static flecs::entity invoke_callback( ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) { size_t count = static_cast(iter->count); @@ -23459,30 +24369,36 @@ struct each_invoker : public invoker { } flecs::iter it(iter); + flecs::entity result; ECS_TABLE_LOCK(iter->world, iter->table); for (size_t i = 0; i < count; i ++) { - func( (ColumnType< remove_reference_t >(comps, i) - .get_row())...); + 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 void invoke_callback(ecs_iter_t *iter, const Func& func, + static flecs::entity invoke_callback(ecs_iter_t *iter, const Func& func, size_t index, Terms& columns, Args... comps) { - invoke_callback( + return invoke_callback( iter, func, index + 1, columns, comps..., columns[index]); - } + } Func m_func; }; - //////////////////////////////////////////////////////////////////////////////// //// Utility class to invoke a system iterate action //////////////////////////////////////////////////////////////////////////////// @@ -23595,13 +24511,12 @@ struct entity_with_invoker_impl> { return true; } - static bool get_ptrs(world_t *world, const ecs_record_t *r, ecs_table_t *table, + 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); - - ecs_table_t *storage_table = ecs_table_get_storage_table(table); - if (!storage_table) { + if (!ecs_table_column_count(table)) { return false; } @@ -23610,8 +24525,8 @@ struct entity_with_invoker_impl> { /* Get column indices for components */ ColumnArray columns ({ - ecs_search_offset(real_world, storage_table, 0, - _::cpp_type().id(world), 0)... + ecs_table_get_column_index(real_world, table, + _::cpp_type().id(world))... }); /* Get pointers for columns for entity */ @@ -23863,6 +24778,12 @@ struct iterable { this->next_each_action()); } + template + flecs::entity find(Func&& func) const { + return iterate_find<_::find_invoker>(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: @@ -23958,6 +24879,23 @@ struct iterable { Invoker(func).invoke(&it); } } + + template < template class Invoker, 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 (Invoker::instanced()) { + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + } + + flecs::entity result; + while (!result && next(&it, FLECS_FWD(args)...)) { + result = Invoker(func).invoke(&it); + } + if (result) { + ecs_iter_fini(&it); + } + return result; + } }; template @@ -23988,7 +24926,6 @@ struct iter_iterable final : iterable { */ iter_iterable& set_var(const char *name, flecs::entity_t value) { - ecs_assert(m_it.next == ecs_rule_next, ECS_INVALID_OPERATION, NULL); 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); @@ -24509,25 +25446,6 @@ struct untyped_component : entity { * @{ */ -/** Add member. */ -untyped_component& member(flecs::entity_t type_id, 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.count = count; - m.offset = static_cast(offset); - e.set(m); - - return *this; -} - /** 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 = {}; @@ -24548,6 +25466,11 @@ untyped_component& member(flecs::entity_t type_id, flecs::entity_t unit, const c 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) { @@ -24570,6 +25493,31 @@ untyped_component& member(const char *name, int32_t count = 0, size_t offset = 0 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)); @@ -24604,6 +25552,55 @@ untyped_component& bit(const char *name, uint32_t value) { 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 @@ -25009,52 +26006,100 @@ struct table { return ecs_table_count(m_table); } - /** Find index for (component) id. + /** 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 search(flecs::id_t id) const { - return ecs_search(m_world, m_table, id, 0); + int32_t column_index(flecs::id_t id) const { + return ecs_table_get_column_index(m_world, m_table, id); } - /** Find index for type. + /** Find column index for type. * * @tparam T The type. * @return True if the table has the type, false if not. */ template - int32_t search() const { - return search(_::cpp_type::id(m_world)); + int32_t column_index() const { + return column_index(_::cpp_type::id(m_world)); } - /** Find index for pair. + /** 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 search(flecs::entity_t first, flecs::entity_t second) const { - return search(ecs_pair(first, second)); + int32_t column_index(flecs::entity_t first, flecs::entity_t second) const { + return column_index(ecs_pair(first, second)); } - /** Find index for pair. + /** 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 search(flecs::entity_t second) const { - return search(_::cpp_type::id(m_world), second); + int32_t column_index(flecs::entity_t second) const { + return column_index(_::cpp_type::id(m_world), second); } - /** Find index for pair. + /** 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 search() const { - return search(_::cpp_type::id(m_world)); + int32_t column_index() const { + return column_index(_::cpp_type::id(m_world)); } /** Test if table has (component) id. @@ -25063,7 +26108,7 @@ struct table { * @return True if the table has the id, false if not. */ bool has(flecs::id_t id) const { - return search(id) != -1; + return type_index(id) != -1; } /** Test if table has the type. @@ -25073,7 +26118,7 @@ struct table { */ template bool has() const { - return search() != -1; + return type_index() != -1; } /** Test if table has the pair. @@ -25083,7 +26128,7 @@ struct table { * @return True if the table has the pair, false if not. */ bool has(flecs::entity_t first, flecs::entity_t second) const { - return search(first, second) != -1; + return type_index(first, second) != -1; } /** Test if table has the pair. @@ -25094,7 +26139,7 @@ struct table { */ template bool has(flecs::entity_t second) const { - return search(second) != -1; + return type_index(second) != -1; } /** Test if table has the pair. @@ -25105,7 +26150,7 @@ struct table { */ template bool has() const { - return search() != -1; + return type_index() != -1; } /** Get pointer to component array by column index. @@ -25113,7 +26158,7 @@ struct table { * @param index The column index. * @return Pointer to the column, NULL if not a component. */ - virtual void* get_by_index(int32_t index) const { + virtual void* get_column(int32_t index) const { return ecs_table_get_column(m_table, index, 0); } @@ -25123,11 +26168,11 @@ struct table { * @return Pointer to the column, NULL if not found. */ void* get(flecs::id_t id) const { - int32_t index = search(id); + int32_t index = column_index(id); if (index == -1) { return NULL; } - return get_by_index(index); + return get_column(index); } /** Get pointer to component array by pair. @@ -25150,6 +26195,16 @@ struct table { 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. @@ -25185,8 +26240,8 @@ struct table { } /** Get column size */ - size_t column_size(int32_t column_index) { - return ecs_table_get_column_size(m_table, column_index); + size_t column_size(int32_t index) { + return ecs_table_get_column_size(m_table, index); } /** Get depth for given relationship. @@ -25242,7 +26297,7 @@ struct table_range : table { * @param index The column index. * @return Pointer to the column, NULL if not a component. */ - void* get_by_index(int32_t index) const override { + void* get_column(int32_t index) const override { return ecs_table_get_column(m_table, index, m_offset); } @@ -25346,6 +26401,9 @@ inline flecs::id world::pair() const { 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( @@ -25354,6 +26412,9 @@ inline flecs::id world::pair(entity_t o) const { } 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)); @@ -25544,9 +26605,9 @@ inline bool entity_view::get(const Func& func) const { return _::entity_with_invoker::invoke_get(m_world, m_id, func); } -inline flecs::entity entity_view::lookup(const char *path) const { +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, "::", "::", false); + auto id = ecs_lookup_path_w_sep(m_world, m_id, path, "::", "::", search_path); return flecs::entity(m_world, id); } @@ -25567,9 +26628,9 @@ inline flecs::entity world::entity(Args &&... args) const { } template ::value >> -inline flecs::entity world::id(E value) const { +inline flecs::id world::id(E value) const { flecs::entity_t constant = enum_type(m_world).entity(value); - return flecs::entity(m_world, constant); + return flecs::id(m_world, constant); } template ::value >> @@ -26097,7 +27158,12 @@ struct term_builder_i : term_id_builder_i { } ecs_assert(sid != 0, ECS_INVALID_PARAMETER, NULL); - m_term->src.id = sid; + + if (!ECS_IS_PAIR(sid)) { + m_term->src.id = sid; + } else { + m_term->src.id = ecs_pair_first(world(), sid); + } return *this; } @@ -26663,8 +27729,8 @@ struct filter_builder final : _::filter_builder_base { if (name != nullptr) { ecs_entity_desc_t entity_desc = {}; entity_desc.name = name; - entity_desc.sep = "::", - entity_desc.root_sep = "::", + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; this->m_desc.entity = ecs_entity_init(world, &entity_desc); } } @@ -26777,6 +27843,7 @@ struct filter_base { 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 } } @@ -26809,7 +27876,7 @@ struct filter : filter_base, iterable { public: using filter_base::filter_base; - filter() : filter_base() { } // necessary not not confuse msvc + filter() : filter_base() { } // necessary not to confuse msvc filter(const filter& obj) : filter_base(obj) { } @@ -27160,8 +28227,8 @@ struct query_builder final : _::query_builder_base { if (name != nullptr) { ecs_entity_desc_t entity_desc = {}; entity_desc.name = name; - entity_desc.sep = "::", - entity_desc.root_sep = "::", + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; this->m_desc.filter.entity = ecs_entity_init(world, &entity_desc); } } @@ -27260,13 +28327,7 @@ struct query_base { template void each_term(const Func& func) const { - const ecs_filter_t *f = ecs_query_get_filter(m_query); - ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); - - for (int i = 0; i < f->term_count; i ++) { - flecs::term t(m_world, f->terms[i]); - func(t); - } + this->filter().each_term(func); } filter_base filter() const { @@ -27577,7 +28638,7 @@ struct observer final : entity } void* ctx() const { - return ecs_get_observer_ctx(m_world, m_id); + return ecs_observer_get_ctx(m_world, m_id); } flecs::filter<> query() const { @@ -27651,7 +28712,7 @@ ecs_entity_t do_import(world& world, const char *symbol) { ecs_set_scope(world, scope); // It should now be possible to lookup the module - ecs_entity_t m = ecs_lookup_symbol(world, symbol, true); + 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); @@ -27664,7 +28725,7 @@ template flecs::entity import(world& world) { const char *symbol = _::symbol_name(); - ecs_entity_t m = ecs_lookup_symbol(world, symbol, true); + ecs_entity_t m = ecs_lookup_symbol(world, symbol, true, false); if (!_::cpp_type::registered(world)) { @@ -27698,7 +28759,7 @@ flecs::entity import(world& world) { template inline flecs::entity world::module(const char *name) const { - flecs::id_t result = _::cpp_type::id(m_world); + flecs::id_t result = _::cpp_type::id(m_world, nullptr, false); if (name) { ecs_add_path_w_sep(m_world, result, 0, name, "::", "::"); } @@ -27994,7 +29055,7 @@ struct system final : entity } void* ctx() const { - return ecs_get_system_ctx(m_world, m_id); + return ecs_system_get_ctx(m_world, m_id); } flecs::query<> query() const { @@ -28208,25 +29269,15 @@ inline void world::run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_tim return ecs_run_pipeline(m_world, pip, delta_time); } -inline void world::set_time_scale(ecs_ftime_t mul) const { - ecs_set_time_scale(m_world, mul); -} - -inline ecs_ftime_t world::get_time_scale() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->time_scale; +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 int64_t world::get_tick() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->frame_count_total; +inline void world::set_time_scale(ecs_ftime_t mul) const { + ecs_set_time_scale(m_world, mul); } -inline ecs_ftime_t world::get_target_fps() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->target_fps; -} - inline void world::set_target_fps(ecs_ftime_t target_fps) const { ecs_set_target_fps(m_world, target_fps); } @@ -28305,6 +29356,10 @@ 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); } @@ -28555,8 +29610,8 @@ struct rule_builder final : _::rule_builder_base { if (name != nullptr) { ecs_entity_desc_t entity_desc = {}; entity_desc.name = name; - entity_desc.sep = "::", - entity_desc.root_sep = "::", + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; this->m_desc.entity = ecs_entity_init(world, &entity_desc); } } @@ -28610,6 +29665,11 @@ struct rule_base { } } + template + void each_term(const Func& func) const { + this->filter().each_term(func); + } + /** Move the rule. */ void move(flecs::rule_base&& obj) { this->destruct(); @@ -28658,7 +29718,7 @@ struct rule final : rule_base, iterable { if (!world) { world = m_world; } - return ecs_rule_iter(m_world, m_rule); + return ecs_rule_iter(world, m_rule); } ecs_iter_next_action_t next_action() const override { @@ -29143,6 +30203,18 @@ inline metric_builder& metric_builder::member(const char *name) { return member(m); } +inline metric_builder& metric_builder::dotmember(const char *expr) { + m_desc.dotmember = expr; + return *this; +} + +template +inline metric_builder& metric_builder::dotmember(const char *expr) { + m_desc.dotmember = expr; + m_desc.id = _::cpp_type::id(m_world); + return *this; +} + inline metric_builder::operator flecs::entity() { if (!m_created) { m_created = true; @@ -29161,27 +30233,20 @@ inline flecs::metric_builder world::metric(Args &&... args) const { } template -inline untyped_component& untyped_component::metric(flecs::entity_t parent, const char *brief, const char *metric_name) { +inline untyped_component& untyped_component::metric( + flecs::entity_t parent, + const char *brief, + const char *metric_name) +{ flecs::world w(m_world); - flecs::entity e = w.entity(m_id); - const flecs::Struct *s = e.get(); - if (!s) { - flecs::log::err("can't register metric, component '%s' is not a struct", - e.path().c_str()); - return *this; - } + flecs::entity e(m_world, m_id); - ecs_member_t *m = ecs_vec_get_t(&s->members, ecs_member_t, - ecs_vec_count(&s->members) - 1); - ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); - - flecs::entity me = e.lookup(m->name); - if (!me) { - flecs::log::err("can't find member '%s' in component '%s' for metric", - m->name, e.path().c_str()); + const flecs::member_t *m = ecs_cpp_last_member(w, e); + if (!m) { return *this; } + flecs::entity me = w.entity(m->member); flecs::entity metric_entity = me; if (parent) { const char *component_name = e.name(); @@ -29268,6 +30333,15 @@ struct alert_builder_i : filter_builder_i { return *this; } + /** Set doc name for alert. + * + * @see ecs_alert_desc_t::doc_name + */ + Base& doc_name(const char *doc_name) { + m_desc->doc_name = doc_name; + return *this; + } + /** Set severity of alert (default is Error) * * @see ecs_alert_desc_t::severity @@ -29277,6 +30351,15 @@ struct alert_builder_i : filter_builder_i { return *this; } + /* Set retain period of alert. + * + * @see ecs_alert_desc_t::retain_period + */ + Base& retain_period(ecs_ftime_t period) { + m_desc->retain_period = period; + return *this; + } + /** Set severity of alert (default is Error) * * @see ecs_alert_desc_t::severity @@ -29286,6 +30369,72 @@ struct alert_builder_i : filter_builder_i { return severity(_::cpp_type::id(world_v())); } + /** Add severity filter */ + Base& severity_filter(flecs::entity_t kind, flecs::id_t with, const char *var = nullptr) { + ecs_assert(severity_filter_count < ECS_ALERT_MAX_SEVERITY_FILTERS, + ECS_INVALID_PARAMETER, "Maxium number of severity filters reached"); + + ecs_alert_severity_filter_t *filter = + &m_desc->severity_filters[severity_filter_count ++]; + + filter->severity = kind; + filter->with = with; + filter->var = var; + return *this; + } + + /** Add severity filter */ + template + Base& severity_filter(flecs::id_t with, const char *var = nullptr) { + return severity_filter(_::cpp_type::id(world_v()), with, var); + } + + /** Add severity filter */ + template ::value > = 0> + Base& severity_filter(const char *var = nullptr) { + return severity_filter(_::cpp_type::id(world_v()), + _::cpp_type::id(world_v()), var); + } + + /** Add severity filter */ + template ::value > = 0 > + Base& severity_filter(T with, const char *var = nullptr) { + flecs::world w(world_v()); + flecs::entity constant = w.to_entity(with); + return severity_filter(_::cpp_type::id(world_v()), + w.pair(constant), var); + } + + /** Set member to create an alert for out of range values */ + Base& member(flecs::entity_t m) { + m_desc->member = m; + return *this; + } + + /** Set (component) id for member (optional). If .member() is set and id + * is not set, the id will default to the member parent. */ + Base& id(flecs::id_t id) { + m_desc->id = id; + return *this; + } + + /** Set member to create an alert for out of range values */ + template + Base& member(const char *m, const char *v = nullptr) { + flecs::entity_t id = _::cpp_type::id(world_v()); + flecs::entity_t mid = ecs_lookup_path_w_sep( + world_v(), id, m, "::", "::", false); + ecs_assert(m != 0, ECS_INVALID_PARAMETER, NULL); + m_desc->var = v; + return this->member(mid); + } + + /** Set source variable for member (optional, defaults to $this) */ + Base& var(const char *v) { + m_desc->var = v; + return *this; + } + protected: virtual flecs::world_t* world_v() = 0; @@ -29295,6 +30444,7 @@ struct alert_builder_i : filter_builder_i { } ecs_alert_desc_t *m_desc; + int32_t severity_filter_count = 0; }; } @@ -29321,8 +30471,8 @@ struct alert_builder final : _::alert_builder_base { if (name != nullptr) { ecs_entity_desc_t entity_desc = {}; entity_desc.name = name; - entity_desc.sep = "::", - entity_desc.root_sep = "::", + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; this->m_desc.entity = ecs_entity_init(world, &entity_desc); } } @@ -29414,8 +30564,8 @@ inline flecs::entity iter::src(int32_t index) const { return flecs::entity(m_iter->world, ecs_field_src(m_iter, index)); } -inline flecs::entity iter::id(int32_t index) const { - return flecs::entity(m_iter->world, ecs_field_id(m_iter, index)); +inline flecs::id iter::id(int32_t index) const { + return flecs::id(m_iter->world, ecs_field_id(m_iter, index)); } inline flecs::id iter::pair(int32_t index) const { @@ -29431,17 +30581,16 @@ inline flecs::type iter::type() const { } inline flecs::table iter::table() const { - return flecs::table(m_iter->world, m_iter->table); + return flecs::table(m_iter->real_world, m_iter->table); } inline flecs::table_range iter::range() const { - return flecs::table_range(m_iter->world, m_iter->table, + return flecs::table_range(m_iter->real_world, m_iter->table, m_iter->offset, m_iter->count); } #ifdef FLECS_RULES inline flecs::entity iter::get_var(int var_id) const { - ecs_assert(m_iter->next == ecs_rule_next, ECS_INVALID_OPERATION, NULL); ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); return flecs::entity(m_iter->world, ecs_iter_get_var(m_iter, var_id)); } @@ -29450,7 +30599,6 @@ inline flecs::entity iter::get_var(int var_id) const { * Get value of a query variable for current result. */ inline flecs::entity iter::get_var(const char *name) const { - ecs_assert(m_iter->next == ecs_rule_next, ECS_INVALID_OPERATION, NULL); ecs_rule_iter_t *rit = &m_iter->priv.iter.rule; const flecs::rule_t *r = rit->rule; int var_id = ecs_rule_find_var(r, name); @@ -29514,9 +30662,9 @@ inline void world::use(flecs::entity e, const char *alias) const { const char *name = alias; if (!name) { // If no name is defined, use the entity name without the scope - ecs_get_name(m_world, eid); + name = ecs_get_name(m_world, eid); } - ecs_set_alias(m_world, eid, alias); + ecs_set_alias(m_world, eid, name); } inline flecs::entity world::set_scope(const flecs::entity_t s) const { @@ -29532,8 +30680,8 @@ inline flecs::entity world::set_scope() const { return set_scope( _::cpp_type::id(m_world) ); } -inline entity world::lookup(const char *name) const { - auto e = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::", true); +inline entity world::lookup(const char *name, bool search_path) const { + auto e = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::", search_path); return flecs::entity(*this, e); } @@ -29546,7 +30694,7 @@ inline T* world::get_mut() const { template inline void world::modified() const { flecs::entity e(m_world, _::cpp_type::id(m_world)); - return e.modified(); + e.modified(); } template @@ -29664,6 +30812,30 @@ inline flecs::entity world::singleton() const { return flecs::entity(m_world, _::cpp_type::id(m_world)); } +template +inline flecs::entity world::target(int32_t index) const +{ + return flecs::entity(m_world, + ecs_get_target(m_world, _::cpp_type::id(m_world), _::cpp_type::id(m_world), index)); +} + +template +inline flecs::entity world::target( + flecs::entity_t relationship, + int32_t index) const +{ + return flecs::entity(m_world, + ecs_get_target(m_world, _::cpp_type::id(m_world), relationship, index)); +} + +inline flecs::entity world::target( + flecs::entity_t relationship, + int32_t index) const +{ + return flecs::entity(m_world, + ecs_get_target(m_world, relationship, relationship, index)); +} + template ::value > > inline void world::get(const Func& func) const { static_assert(arity::value == 1, "singleton component must be the only argument"); @@ -29719,6 +30891,10 @@ inline flecs::scoped_world world::scope() const { return scoped_world(m_world, parent); } +inline flecs::scoped_world world::scope(const char* name) const { + return scope(entity(name)); +} + } // namespace flecs diff --git a/flecs-sys/src/bindings.rs b/flecs-sys/src/bindings.rs index 531c46e..78f3dd9 100644 --- a/flecs-sys/src/bindings.rs +++ b/flecs-sys/src/bindings.rs @@ -108,9 +108,10 @@ pub const EcsTableHasRemoveActions: u32 = 656392; pub const EcsQueryHasRefs: u32 = 2; pub const EcsQueryIsSubquery: u32 = 4; pub const EcsQueryIsOrphaned: u32 = 8; -pub const EcsQueryHasOutColumns: u32 = 16; -pub const EcsQueryHasMonitor: u32 = 32; -pub const EcsQueryTrivialIter: u32 = 64; +pub const EcsQueryHasOutTerms: u32 = 16; +pub const EcsQueryHasNonThisOutTerms: u32 = 32; +pub const EcsQueryHasMonitor: u32 = 64; +pub const EcsQueryTrivialIter: u32 = 128; pub const EcsAperiodicEmptyTables: u32 = 2; pub const EcsAperiodicComponentMonitors: u32 = 4; pub const EcsAperiodicEmptyQueries: u32 = 16; @@ -152,6 +153,8 @@ pub const EcsTermSrcSecondEq: u32 = 8; pub const EcsTermTransitive: u32 = 16; pub const EcsTermReflexive: u32 = 32; pub const EcsTermIdInherited: u32 = 64; +pub const EcsTermMatchDisabled: u32 = 128; +pub const EcsTermMatchPrefab: u32 = 256; pub const flecs_iter_cache_ids: u32 = 1; pub const flecs_iter_cache_columns: u32 = 2; pub const flecs_iter_cache_sources: u32 = 4; @@ -161,8 +164,7 @@ pub const flecs_iter_cache_variables: u32 = 32; pub const flecs_iter_cache_all: u32 = 255; pub const ECS_MAX_RECURSION: u32 = 512; pub const ECS_MAX_TOKEN_SIZE: u32 = 256; -pub const FLECS__E0: u32 = 0; -pub const ECS_ID_FLAG_BIT: i64 = -9223372036854775808; +pub const FLECS_ID0ID_: u32 = 0; pub const EcsFirstUserComponentId: u32 = 8; pub const EcsFirstUserEntityId: u32 = 384; pub const ECS_INVALID_OPERATION: u32 = 1; @@ -179,6 +181,7 @@ pub const ECS_INVALID_CONVERSION: u32 = 11; pub const ECS_ID_IN_USE: u32 = 12; pub const ECS_CYCLE_DETECTED: u32 = 13; pub const ECS_LEAK_DETECTED: u32 = 14; +pub const ECS_DOUBLE_FREE: u32 = 15; pub const ECS_INCONSISTENT_NAME: u32 = 20; pub const ECS_NAME_IN_USE: u32 = 21; pub const ECS_NOT_A_COMPONENT: u32 = 22; @@ -213,9 +216,10 @@ pub const ECS_HTTP_HEADER_COUNT_MAX: u32 = 32; pub const ECS_HTTP_QUERY_PARAM_COUNT_MAX: u32 = 32; pub const ECS_REST_DEFAULT_PORT: u32 = 27750; pub const ECS_STAT_WINDOW: u32 = 60; +pub const ECS_ALERT_MAX_SEVERITY_FILTERS: u32 = 4; pub const ECS_MEMBER_DESC_CACHE_SIZE: u32 = 32; pub const ECS_META_MAX_SCOPE_DEPTH: u32 = 32; -pub type va_list = *mut ::std::os::raw::c_char; +pub type va_list = __builtin_va_list; #[doc = "Utility types to indicate usage as bitmask"] pub type ecs_flags8_t = u8; pub type ecs_flags16_t = u16; @@ -230,7 +234,6 @@ pub struct ecs_vec_t { pub array: *mut ::std::os::raw::c_void, pub count: i32, pub size: i32, - pub elem_size: ecs_size_t, } extern "C" { pub fn ecs_vec_init( @@ -272,7 +275,7 @@ extern "C" { extern "C" { pub fn ecs_vec_copy( allocator: *mut ecs_allocator_t, - vec: *mut ecs_vec_t, + vec: *const ecs_vec_t, size: ecs_size_t, ) -> ecs_vec_t; } @@ -621,7 +624,7 @@ extern "C" { } extern "C" { #[doc = "Get element as pointer (auto-dereferences _ptr)"] - pub fn _ecs_map_get_deref( + pub fn ecs_map_get_deref_( map: *const ecs_map_t, key: ecs_map_key_t, ) -> *mut ::std::os::raw::c_void; @@ -809,6 +812,10 @@ extern "C" { nan_delim: ::std::os::raw::c_char, ) -> bool; } +extern "C" { + #[doc = "Append boolean to buffer.\n Returns false when max is reached, true when there is still space"] + pub fn ecs_strbuf_appendbool(buffer: *mut ecs_strbuf_t, v: bool) -> bool; +} extern "C" { #[doc = "Append source buffer to destination buffer.\n Returns false when max is reached, true when there is still space"] pub fn ecs_strbuf_mergebuff( @@ -1217,11 +1224,11 @@ extern "C" { #[doc = "Are module path functions available?"] pub fn ecs_os_has_modules() -> bool; } -#[doc = "An id. Ids are the things that can be added to an entity. An id can be an\n entity or pair, and can have optional id flags."] +#[doc = "Ids are the things that can be added to an entity.\n An id can be an entity or pair, and can have optional id flags."] pub type ecs_id_t = u64; -#[doc = "An entity identifier."] +#[doc = "An entity identifier.\n Entity ids consist out of a number unique to the entity in the lower 32 bits,\n and a counter used to track entity liveliness in the upper 32 bits. When an\n id is recycled, its generation count is increased. This causes recycled ids\n to be very large (>4 billion), which is normal."] pub type ecs_entity_t = ecs_id_t; -#[doc = "An array with (component) ids"] +#[doc = "A type is a list of (component) ids.\n Types are used to communicate the \"type\" of an entity. In most type systems a\n typeof operation returns a single type. In ECS however, an entity can have\n multiple components, which is why an ECS type consists of a vector of ids.\n\n The component ids of a type are sorted, which ensures that it doesn't matter\n in which order components are added to an entity. For example, if adding\n Position then Velocity would result in type \\[Position, Velocity\\], first\n adding Velocity then Position would also result in type \\[Position, Velocity\\].\n\n Entities are grouped together by type in the ECS storage in tables. The\n storage has exactly one table per unique type that is created by the\n application that stores all entities and components for that type. This is\n also referred to as an archetype."] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_type_t { @@ -1407,7 +1414,7 @@ pub const ecs_inout_kind_t_EcsIn: ecs_inout_kind_t = 3; #[doc = "< Term is only written"] pub const ecs_inout_kind_t_EcsOut: ecs_inout_kind_t = 4; #[doc = "Specify read/write access for term"] -pub type ecs_inout_kind_t = ::std::os::raw::c_int; +pub type ecs_inout_kind_t = ::std::os::raw::c_uint; #[doc = "< The term must match"] pub const ecs_oper_kind_t_EcsAnd: ecs_oper_kind_t = 0; #[doc = "< One of the terms in an or chain must match"] @@ -1423,15 +1430,15 @@ pub const ecs_oper_kind_t_EcsOrFrom: ecs_oper_kind_t = 5; #[doc = "< Term must match none of the components from term id"] pub const ecs_oper_kind_t_EcsNotFrom: ecs_oper_kind_t = 6; #[doc = "Specify operator for term"] -pub type ecs_oper_kind_t = ::std::os::raw::c_int; +pub type ecs_oper_kind_t = ::std::os::raw::c_uint; #[doc = "Type that describes a single identifier in a term"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_term_id_t { #[doc = "< Entity id. If left to 0 and flags does not\n specify whether id is an entity or a variable\n the id will be initialized to EcsThis.\n To explicitly set the id to 0, leave the id\n member to 0 and set EcsIsEntity in flags."] pub id: ecs_entity_t, - #[doc = "< Name. This can be either the variable name\n (when the EcsIsVariable flag is set) or an\n entity name. Entity names are used to\n initialize the id member during term\n finalization and will be freed when term.move\n is set to true."] - pub name: *mut ::std::os::raw::c_char, + #[doc = "< Name. This can be either the variable name\n (when the EcsIsVariable flag is set) or an\n entity name. When ecs_term_t::move is true,\n the API assumes ownership over the string and\n will free it when the term is destroyed."] + pub name: *const ::std::os::raw::c_char, #[doc = "< Relationship to traverse when looking for the\n component. The relationship must have\n the Traversable property. Default is IsA."] pub trav: ecs_entity_t, #[doc = "< Term flags"] @@ -1467,7 +1474,7 @@ pub struct ecs_term_t { pub move_: bool, } extern "C" { - #[doc = "Use this variable to initialize user-allocated filter object"] + #[doc = "Use $this variable to initialize user-allocated filter object"] pub static mut ECS_FILTER_INIT: ecs_filter_t; } #[doc = "Filters alllow for ad-hoc quick filtering of entity tables."] @@ -1674,7 +1681,7 @@ pub struct ecs_ref_t { #[doc = "Entity index record"] pub record: *mut ecs_record_t, } -#[doc = "Cursor to stack allocator (used internally)"] +#[doc = "Cursor to stack allocator. Type is public to allow for white box testing."] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_stack_page_t { @@ -1683,8 +1690,11 @@ pub struct ecs_stack_page_t { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_stack_cursor_t { - pub cur: *mut ecs_stack_page_t, + pub prev: *mut ecs_stack_cursor_t, + pub page: *mut ecs_stack_page_t, pub sp: i16, + pub is_free: bool, + pub owner: *mut ecs_stack_t, } #[doc = "Page-iterator specific data"] #[repr(C)] @@ -1736,7 +1746,7 @@ pub const ecs_iter_kind_t_EcsIterEvalCondition: ecs_iter_kind_t = 0; pub const ecs_iter_kind_t_EcsIterEvalTables: ecs_iter_kind_t = 1; pub const ecs_iter_kind_t_EcsIterEvalChain: ecs_iter_kind_t = 2; pub const ecs_iter_kind_t_EcsIterEvalNone: ecs_iter_kind_t = 3; -pub type ecs_iter_kind_t = ::std::os::raw::c_int; +pub type ecs_iter_kind_t = ::std::os::raw::c_uint; #[doc = "Filter-iterator specific data"] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -1797,7 +1807,7 @@ pub struct ecs_rule_iter_t { #[derive(Debug, Copy, Clone)] pub struct ecs_iter_cache_t { #[doc = "Stack cursor to restore to"] - pub stack_cursor: ecs_stack_cursor_t, + pub stack_cursor: *mut ecs_stack_cursor_t, #[doc = "For which fields is the cache used"] pub used: ecs_flags8_t, #[doc = "Which fields are allocated"] @@ -1905,6 +1915,8 @@ pub struct ecs_iter_t { pub next: ecs_iter_next_action_t, #[doc = "Callback of system or observer"] pub callback: ecs_iter_action_t, + #[doc = "Invoked after setting variable (optionally set)"] + pub set_var: ecs_iter_action_t, #[doc = "Function to cleanup iterator resources"] pub fini: ecs_iter_fini_action_t, #[doc = "Optional, allows for creating iterator chains"] @@ -1944,6 +1956,9 @@ extern "C" { extern "C" { pub fn flecs_table_observed_count(table: *const ecs_table_t) -> i32; } +extern "C" { + pub fn flecs_dump_backtrace(stream: *mut ::std::os::raw::c_void); +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_hm_bucket_t { @@ -1976,7 +1991,7 @@ pub struct flecs_hashmap_result_t { pub hash: u64, } extern "C" { - pub fn _flecs_hashmap_init( + pub fn flecs_hashmap_init_( hm: *mut ecs_hashmap_t, key_size: ecs_size_t, value_size: ecs_size_t, @@ -1989,7 +2004,7 @@ extern "C" { pub fn flecs_hashmap_fini(map: *mut ecs_hashmap_t); } extern "C" { - pub fn _flecs_hashmap_get( + pub fn flecs_hashmap_get_( map: *const ecs_hashmap_t, key_size: ecs_size_t, key: *const ::std::os::raw::c_void, @@ -1997,7 +2012,7 @@ extern "C" { ) -> *mut ::std::os::raw::c_void; } extern "C" { - pub fn _flecs_hashmap_ensure( + pub fn flecs_hashmap_ensure_( map: *mut ecs_hashmap_t, key_size: ecs_size_t, key: *const ::std::os::raw::c_void, @@ -2005,7 +2020,7 @@ extern "C" { ) -> flecs_hashmap_result_t; } extern "C" { - pub fn _flecs_hashmap_set( + pub fn flecs_hashmap_set_( map: *mut ecs_hashmap_t, key_size: ecs_size_t, key: *mut ::std::os::raw::c_void, @@ -2014,7 +2029,7 @@ extern "C" { ); } extern "C" { - pub fn _flecs_hashmap_remove( + pub fn flecs_hashmap_remove_( map: *mut ecs_hashmap_t, key_size: ecs_size_t, key: *const ::std::os::raw::c_void, @@ -2022,7 +2037,7 @@ extern "C" { ); } extern "C" { - pub fn _flecs_hashmap_remove_w_hash( + pub fn flecs_hashmap_remove_w_hash_( map: *mut ecs_hashmap_t, key_size: ecs_size_t, key: *const ::std::os::raw::c_void, @@ -2048,7 +2063,7 @@ extern "C" { pub fn flecs_hashmap_iter(map: *mut ecs_hashmap_t) -> flecs_hashmap_iter_t; } extern "C" { - pub fn _flecs_hashmap_next( + pub fn flecs_hashmap_next_( it: *mut flecs_hashmap_iter_t, key_size: ecs_size_t, key_out: *mut ::std::os::raw::c_void, @@ -2082,7 +2097,7 @@ pub struct ecs_entity_desc_t { #[derive(Debug, Copy, Clone)] pub struct ecs_bulk_desc_t { pub _canary: i32, - #[doc = "< Entities to bulk insert. Entity ids provided by\n the application application must be empty (cannot\n have components). If no entity ids are provided, the\n operation will create 'count' new entities."] + #[doc = "< Entities to bulk insert. Entity ids provided by\n the application must be empty (cannot\n have components). If no entity ids are provided, the\n operation will create 'count' new entities."] pub entities: *mut ecs_entity_t, #[doc = "< Number of entities to create/populate"] pub count: i32, @@ -2152,6 +2167,14 @@ pub struct ecs_query_desc_t { pub group_by_ctx_free: ecs_ctx_free_t, #[doc = "If set, the query will be created as a subquery. A subquery matches at\n most a subset of its parent query. Subqueries do not directly receive\n (table) notifications from the world. Instead parent queries forward\n results to subqueries. This can improve matching performance, as fewer\n queries need to be matched with new tables.\n Subqueries can be nested."] pub parent: *mut ecs_query_t, + #[doc = "User context to pass to callback"] + pub ctx: *mut ::std::os::raw::c_void, + #[doc = "Context to be used for language bindings"] + pub binding_ctx: *mut ::std::os::raw::c_void, + #[doc = "Callback to free ctx"] + pub ctx_free: ecs_ctx_free_t, + #[doc = "Callback to free binding_ctx"] + pub binding_ctx_free: ecs_ctx_free_t, } #[doc = "Used with ecs_observer_init.\n\n \\ingroup observers"] #[repr(C)] @@ -2185,6 +2208,31 @@ pub struct ecs_observer_desc_t { #[doc = "Used for internal purposes"] pub term_index: i32, } +#[doc = "Used with ecs_emit.\n\n \\ingroup observers"] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ecs_event_desc_t { + #[doc = "The event id. Only triggers for the specified event will be notified"] + pub event: ecs_entity_t, + #[doc = "Component ids. Only triggers with a matching component id will be\n notified. Observers are guaranteed to get notified once, even if they\n match more than one id."] + pub ids: *const ecs_type_t, + #[doc = "The table for which to notify."] + pub table: *mut ecs_table_t, + #[doc = "Optional 2nd table to notify. This can be used to communicate the\n previous or next table, in case an entity is moved between tables."] + pub other_table: *mut ecs_table_t, + #[doc = "Limit notified entities to ones starting from offset (row) in table"] + pub offset: i32, + #[doc = "Limit number of notified entities to count. offset+count must be less\n than the total number of entities in the table. If left to 0, it will be\n automatically determined by doing ecs_table_count(table) - offset."] + pub count: i32, + #[doc = "Single-entity alternative to setting table / offset / count"] + pub entity: ecs_entity_t, + #[doc = "Optional context. Assigned to iter param member"] + pub param: *const ::std::os::raw::c_void, + #[doc = "Observable (usually the world)"] + pub observable: *mut ecs_poly_t, + #[doc = "Event flags"] + pub flags: ecs_flags32_t, +} #[doc = "Utility to hold a value of a dynamic type"] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -2365,16 +2413,16 @@ extern "C" { pub static ECS_AND: ecs_id_t; } extern "C" { - pub static FLECS__EEcsComponent: ecs_entity_t; + pub static FLECS_IDEcsComponentID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsIdentifier: ecs_entity_t; + pub static FLECS_IDEcsIdentifierID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsIterable: ecs_entity_t; + pub static FLECS_IDEcsIterableID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsPoly: ecs_entity_t; + pub static FLECS_IDEcsPolyID_: ecs_entity_t; } extern "C" { pub static EcsQuery: ecs_entity_t; @@ -2387,16 +2435,16 @@ extern "C" { pub static EcsSystem: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsTickSource: ecs_entity_t; + pub static FLECS_IDEcsTickSourceID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsPipelineQuery: ecs_entity_t; + pub static FLECS_IDEcsPipelineQueryID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsTimer: ecs_entity_t; + pub static FLECS_IDEcsTimerID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsRateFilter: ecs_entity_t; + pub static FLECS_IDEcsRateFilterID_: ecs_entity_t; } extern "C" { #[doc = "Root scope for builtin flecs entities"] @@ -2459,7 +2507,7 @@ extern "C" { pub static EcsAcyclic: ecs_entity_t; } extern "C" { - #[doc = "Marks a relationship as traversable. Traversable relationships may be\n traversed with \"up\" queries. Traversable relatinoships are acyclic."] + #[doc = "Marks a relationship as traversable. Traversable relationships may be\n traversed with \"up\" queries. Traversable relationships are acyclic."] pub static EcsTraversable: ecs_entity_t; } extern "C" { @@ -2471,11 +2519,11 @@ extern "C" { pub static EcsOneOf: ecs_entity_t; } extern "C" { - #[doc = "Can be added to relationship to indicate that it should never hold data, even\n when it or the relationship target is a component."] + #[doc = "Can be added to relationship to indicate that it should never hold data,\n even when it or the relationship target is a component."] pub static EcsTag: ecs_entity_t; } extern "C" { - #[doc = "Tag to indicate that relationship is stored as union. Union relationships enable\n changing the target of a union without switching tables. Union relationships\n are also marked as exclusive."] + #[doc = "Tag to indicate that relationship is stored as union. Union relationships\n enable changing the target of a union without switching tables. Union\n relationships are also marked as exclusive."] pub static EcsUnion: ecs_entity_t; } extern "C" { @@ -2515,55 +2563,55 @@ extern "C" { pub static EcsPrivate: ecs_entity_t; } extern "C" { - #[doc = "Tag added to prefab entities. Any entity with this tag is automatically\n ignored by filters/queries, unless EcsPrefab is explicitly added."] + #[doc = "Tag added to prefab entities. Any entity with this tag is automatically\n ignored by queries, unless EcsPrefab is explicitly queried for."] pub static EcsPrefab: ecs_entity_t; } extern "C" { - #[doc = "When this tag is added to an entity it is skipped by all queries/filters"] + #[doc = "When this tag is added to an entity it is skipped by queries, unless\n EcsDisabled is explicitly queried for."] pub static EcsDisabled: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when an id (component, tag, pair) is added to an entity"] + #[doc = "Event that triggers when an id is added to an entity"] pub static EcsOnAdd: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when an id (component, tag, pair) is removed from an entity"] + #[doc = "Event that triggers when an id is removed from an entity"] pub static EcsOnRemove: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when a component is set for an entity"] + #[doc = "Event that triggers when a component is set for an entity"] pub static EcsOnSet: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when a component is unset for an entity"] + #[doc = "Event that triggers when a component is unset for an entity"] pub static EcsUnSet: ecs_entity_t; } extern "C" { - #[doc = "Event. Exactly-once observer for when an entity matches/unmatches a filter"] + #[doc = "Event that triggers observer when an entity starts/stops matching a query"] pub static EcsMonitor: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when an entity is deleted.\n Also used as relationship for defining cleanup behavior, see:\n "] - pub static EcsOnDelete: ecs_entity_t; -} -extern "C" { - #[doc = "Event. Triggers when a table is created."] + #[doc = "Event that triggers when a table is created."] pub static EcsOnTableCreate: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when a table is deleted."] + #[doc = "Event that triggers when a table is deleted."] pub static EcsOnTableDelete: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when a table becomes empty (doesn't emit on creation)."] + #[doc = "Event that triggers when a table becomes empty (doesn't emit on creation)."] pub static EcsOnTableEmpty: ecs_entity_t; } extern "C" { - #[doc = "Event. Triggers when a table becomes non-empty."] + #[doc = "Event that triggers when a table becomes non-empty."] pub static EcsOnTableFill: ecs_entity_t; } extern "C" { - #[doc = "Relationship used to define what should happen when a target entity (second\n element of a pair) is deleted. For details see:\n "] + #[doc = "Relationship used for specifying cleanup behavior."] + pub static EcsOnDelete: ecs_entity_t; +} +extern "C" { + #[doc = "Relationship used to define what should happen when a target entity (second\n element of a pair) is deleted."] pub static EcsOnDeleteTarget: ecs_entity_t; } extern "C" { @@ -2579,9 +2627,10 @@ extern "C" { pub static EcsPanic: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsTarget: ecs_entity_t; + pub static FLECS_IDEcsTargetID_: ecs_entity_t; } extern "C" { + #[doc = "Tag added to root entity to indicate its subtree should be flattened. Used\n together with assemblies."] pub static EcsFlatten: ecs_entity_t; } extern "C" { @@ -2610,7 +2659,7 @@ extern "C" { pub static EcsEmpty: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsPipeline: ecs_entity_t; + pub static FLECS_IDEcsPipelineID_: ecs_entity_t; } extern "C" { pub static EcsOnStart: ecs_entity_t; @@ -2649,15 +2698,15 @@ extern "C" { pub static EcsPhase: ecs_entity_t; } extern "C" { - #[doc = "Create a new world.\n A world manages all the ECS data and supporting infrastructure. Applications\n must have at least one world. Entities, component and system handles are\n local to a world and should not be shared between worlds.\n\n This operation creates a world with all builtin modules loaded.\n\n @return A new world"] + #[doc = "Create a new world.\n This operation automatically imports modules from addons Flecs has been built\n with, except when the module specifies otherwise.\n\n @return A new world"] pub fn ecs_init() -> *mut ecs_world_t; } extern "C" { - #[doc = "Same as ecs_init, but with minimal set of modules loaded.\n\n @return A new tiny world"] + #[doc = "Create a new world with just the core module.\n Same as ecs_init, but doesn't import modules from addons. This operation is\n faster than ecs_init and results in less memory utilization.\n\n @return A new tiny world"] pub fn ecs_mini() -> *mut ecs_world_t; } extern "C" { - #[doc = "Create a new world with arguments.\n Same as ecs_init, but allows passing in command line arguments. These can be\n used to dynamically enable flecs features to an application. Currently these\n arguments are not used.\n\n @return A new world"] + #[doc = "Create a new world with arguments.\n Same as ecs_init, but allows passing in command line arguments. Command line\n arguments are used to:\n - automatically derive the name of the application from argv\\[0\\]\n\n @return A new world"] pub fn ecs_init_w_args( argc: ::std::os::raw::c_int, argv: *mut *mut ::std::os::raw::c_char, @@ -2668,7 +2717,7 @@ extern "C" { pub fn ecs_fini(world: *mut ecs_world_t) -> ::std::os::raw::c_int; } extern "C" { - #[doc = "Returns whether the world is being deleted.\n\n @param world The world.\n @return True if being deleted, false if not."] + #[doc = "Returns whether the world is being deleted.\n This operation can be used in callbacks like type hooks or observers to\n detect if they are invoked while the world is being deleted.\n\n @param world The world.\n @return True if being deleted, false if not."] pub fn ecs_is_fini(world: *const ecs_world_t) -> bool; } extern "C" { @@ -2784,15 +2833,31 @@ extern "C" { pub fn ecs_stage_is_async(stage: *mut ecs_world_t) -> bool; } extern "C" { - #[doc = "Set a world context.\n This operation allows an application to register custom data with a world\n that can be accessed anywhere where the application has the world.\n\n @param world The world.\n @param ctx A pointer to a user defined structure."] - pub fn ecs_set_context(world: *mut ecs_world_t, ctx: *mut ::std::os::raw::c_void); + #[doc = "Set a world context.\n This operation allows an application to register custom data with a world\n that can be accessed anywhere where the application has the world.\n\n @param world The world.\n @param ctx A pointer to a user defined structure.\n @param ctx_free A function that is invoked with ctx when the world is freed."] + pub fn ecs_set_ctx( + world: *mut ecs_world_t, + ctx: *mut ::std::os::raw::c_void, + ctx_free: ecs_ctx_free_t, + ); } extern "C" { - #[doc = "Get the world context.\n This operation retrieves a previously set world context.\n\n @param world The world.\n @return The context set with ecs_set_context. If no context was set, the\n function returns NULL."] - pub fn ecs_get_context(world: *const ecs_world_t) -> *mut ::std::os::raw::c_void; + #[doc = "Set a world binding context.\n Same as ecs_set_ctx but for binding context. A binding context is intended\n specifically for language bindings to store binding specific data.\n\n @param world The world.\n @param ctx A pointer to a user defined structure.\n @param ctx_free A function that is invoked with ctx when the world is freed."] + pub fn ecs_set_binding_ctx( + world: *mut ecs_world_t, + ctx: *mut ::std::os::raw::c_void, + ctx_free: ecs_ctx_free_t, + ); } extern "C" { - #[doc = "Get world info.\n\n @param world The world.\n @return Pointer to the world info. This pointer will remain valid for as long\n as the world is valid."] + #[doc = "Get the world context.\n This operation retrieves a previously set world context.\n\n @param world The world.\n @return The context set with ecs_set_ctx. If no context was set, the\n function returns NULL."] + pub fn ecs_get_ctx(world: *const ecs_world_t) -> *mut ::std::os::raw::c_void; +} +extern "C" { + #[doc = "Get the world binding context.\n This operation retrieves a previously set world binding context.\n\n @param world The world.\n @return The context set with ecs_set_binding_ctx. If no context was set, the\n function returns NULL."] + pub fn ecs_get_binding_ctx(world: *const ecs_world_t) -> *mut ::std::os::raw::c_void; +} +extern "C" { + #[doc = "Get world info.\n\n @param world The world.\n @return Pointer to the world info. Valid for as long as the world exists."] pub fn ecs_get_world_info(world: *const ecs_world_t) -> *const ecs_world_info_t; } extern "C" { @@ -2840,7 +2905,7 @@ extern "C" { } extern "C" { #[doc = "Test if pointer is of specified type.\n Usage:\n ecs_poly_is(ptr, ecs_world_t)\n\n This operation only works for poly types.\n\n @param object The object to test.\n @param type The id of the type.\n @return True if the pointer is of the specified type."] - pub fn _ecs_poly_is(object: *const ecs_poly_t, type_: i32) -> bool; + pub fn ecs_poly_is_(object: *const ecs_poly_t, type_: i32) -> bool; } extern "C" { #[doc = "Make a pair id.\n This function is equivalent to using the ecs_pair macro, and is added for\n convenience to make it easier for non C/C++ bindings to work with pairs.\n\n @param first The first element of the pair of the pair.\n @param second The target of the pair."] @@ -2892,7 +2957,7 @@ extern "C" { ) -> ecs_entity_t; } extern "C" { - #[doc = "Delete an entity.\n This operation will delete an entity and all of its components. The entity id\n will be recycled. Repeatedly calling ecs_delete without ecs_new or\n ecs_new_w_id will cause a memory leak as it will cause\n the list with ids that can be recycled to grow unbounded.\n\n @param world The world.\n @param entity The entity."] + #[doc = "Delete an entity.\n This operation will delete an entity and all of its components. The entity id\n will be made available for recycling. If the entity passed to ecs_delete is\n not alive, the operation will have no side effects.\n\n @param world The world.\n @param entity The entity."] pub fn ecs_delete(world: *mut ecs_world_t, entity: ecs_entity_t); } extern "C" { @@ -2900,11 +2965,11 @@ extern "C" { pub fn ecs_delete_with(world: *mut ecs_world_t, id: ecs_id_t); } extern "C" { - #[doc = "Add a (component) id to an entity.\n This operation adds a single (component) id to an entity. If the entity\n already has the id, this operation has no side effects.\n\n @param world The world.\n @param entity The entity.\n @param id The id to add."] + #[doc = "Add a (component) id to an entity.\n This operation adds a single (component) id to an entity. If the entity\n already has the id, this operation will have no side effects.\n\n @param world The world.\n @param entity The entity.\n @param id The id to add."] pub fn ecs_add_id(world: *mut ecs_world_t, entity: ecs_entity_t, id: ecs_id_t); } extern "C" { - #[doc = "Remove a (component) id from an entity.\n This operation removes a single (component) id to an entity. If the entity\n does not have the id, this operation has no side effects.\n\n @param world The world.\n @param entity The entity.\n @param id The id to remove."] + #[doc = "Remove a (component) id from an entity.\n This operation removes a single (component) id to an entity. If the entity\n does not have the id, this operation will have no side effects.\n\n @param world The world.\n @param entity The entity.\n @param id The id to remove."] pub fn ecs_remove_id(world: *mut ecs_world_t, entity: ecs_entity_t, id: ecs_id_t); } extern "C" { @@ -2912,11 +2977,11 @@ extern "C" { pub fn ecs_override_id(world: *mut ecs_world_t, entity: ecs_entity_t, id: ecs_id_t); } extern "C" { - #[doc = "Clear all components.\n This operation will clear all components from an entity but will not delete\n the entity itself. This effectively prevents the entity id from being\n recycled.\n\n @param world The world.\n @param entity The entity."] + #[doc = "Clear all components.\n This operation will remove all components from an entity.\n\n @param world The world.\n @param entity The entity."] pub fn ecs_clear(world: *mut ecs_world_t, entity: ecs_entity_t); } extern "C" { - #[doc = "Remove all instances of the specified id.\n This will remove the specified id from all entities (tables). Teh id may be\n a wildcard and/or a pair.\n\n @param world The world.\n @param id The id."] + #[doc = "Remove all instances of the specified (component) id.\n This will remove the specified id from all entities (tables). The id may be\n a wildcard and/or a pair.\n\n @param world The world.\n @param id The id."] pub fn ecs_remove_all(world: *mut ecs_world_t, id: ecs_id_t); } extern "C" { @@ -2976,6 +3041,14 @@ extern "C" { id: ecs_id_t, ) -> *mut ::std::os::raw::c_void; } +extern "C" { + #[doc = "Combines get_mut + modifed in single operation.\n This operation is a more efficient alternative to calling ecs_get_mut_id and\n ecs_modified_id separately. This operation is only valid when the world is in\n deferred mode, which ensures that the Modified event is not emitted before\n the modification takes place.\n\n @param world The world.\n @param entity The entity.\n @param id The id of the component to obtain.\n @return The component pointer."] + pub fn ecs_get_mut_modified_id( + world: *mut ecs_world_t, + entity: ecs_entity_t, + id: ecs_id_t, + ) -> *mut ::std::os::raw::c_void; +} extern "C" { #[doc = "Begin exclusive write access to entity.\n This operation provides safe exclusive access to the components of an entity\n without the overhead of deferring operations.\n\n When this operation is called simultaneously for the same entity more than\n once it will throw an assert. Note that for this to happen, asserts must be\n enabled. It is up to the application to ensure that access is exclusive, for\n example by using a read-write mutex.\n\n Exclusive access is enforced at the table level, so only one entity can be\n exclusively accessed per table. The exclusive access check is thread safe.\n\n This operation must be followed up with ecs_write_end.\n\n @param world The world.\n @param entity The entity.\n @return A record to the entity."] pub fn ecs_write_begin(world: *mut ecs_world_t, entity: ecs_entity_t) -> *mut ecs_record_t; @@ -3029,11 +3102,11 @@ extern "C" { ) -> *mut ::std::os::raw::c_void; } extern "C" { - #[doc = "Signal that a component has been modified.\n This operation allows an application to signal to Flecs that a component has\n been modified. As a result, OnSet systems will be invoked.\n\n This operation is commonly used together with ecs_get_mut.\n\n @param world The world.\n @param entity The entity.\n @param id The id of the component that was modified."] + #[doc = "Signal that a component has been modified.\n This operation is usually used after modifying a component value obtained by\n ecs_get_mut_id. The operation will mark the component as dirty, and invoke\n OnSet observers and hooks.\n\n @param world The world.\n @param entity The entity.\n @param id The id of the component that was modified."] pub fn ecs_modified_id(world: *mut ecs_world_t, entity: ecs_entity_t, id: ecs_id_t); } extern "C" { - #[doc = "Set the value of a component.\n This operation allows an application to set the value of a component. The\n operation is equivalent to calling ecs_get_mut and ecs_modified.\n\n If the provided entity is 0, a new entity will be created.\n\n @param world The world.\n @param entity The entity.\n @param id The id of the component to set.\n @param size The size of the pointer to the value.\n @param ptr The pointer to the value.\n @return The entity. A new entity if no entity was provided."] + #[doc = "Set the value of a component.\n This operation allows an application to set the value of a component. The\n operation is equivalent to calling ecs_get_mut_id followed by\n ecs_modified_id. The operation will not modify the value of the passed in\n component. If the component has a copy hook registered, it will be used to\n copy in the component.\n\n If the provided entity is 0, a new entity will be created.\n\n @param world The world.\n @param entity The entity.\n @param id The id of the component to set.\n @param size The size of the pointed-to value.\n @param ptr The pointer to the value.\n @return The entity. A new entity if no entity was provided."] pub fn ecs_set_id( world: *mut ecs_world_t, entity: ecs_entity_t, @@ -3043,11 +3116,11 @@ extern "C" { ) -> ecs_entity_t; } extern "C" { - #[doc = "Test whether an entity is valid.\n Entities that are valid can be used with API functions.\n\n An entity is valid if it is not 0 and if it is alive. If the provided id is\n a pair, the contents of the pair will be checked for validity.\n\n is_valid will return true for ids that don't exist (alive or not alive). This\n allows for using ids that have never been created by ecs_new or similar. In\n this the function differs from ecs_is_alive, which will return false for\n entities that do not yet exist.\n\n The operation will return false for an id that exists and is not alive, as\n using this id with an API operation would cause it to assert.\n\n @param world The world.\n @param e The entity.\n @return True if the entity is valid, false if the entity is not valid."] + #[doc = "Test whether an entity is valid.\n Entities that are valid can be used with API functions. Using invalid\n entities with API operations will cause the function to panic.\n\n An entity is valid if it is not 0 and if it is alive.\n\n is_valid will return true for ids that don't exist (alive or not alive). This\n allows for using ids that have never been created by ecs_new or similar. In\n this the function differs from ecs_is_alive, which will return false for\n entities that do not yet exist.\n\n The operation will return false for an id that exists and is not alive, as\n using this id with an API operation would cause it to assert.\n\n @param world The world.\n @param e The entity.\n @return True if the entity is valid, false if the entity is not valid."] pub fn ecs_is_valid(world: *const ecs_world_t, e: ecs_entity_t) -> bool; } extern "C" { - #[doc = "Test whether an entity is alive.\n An entity is alive when it has been returned by ecs_new (or similar) or if\n it is not empty (componentts have been explicitly added to the id).\n\n @param world The world.\n @param e The entity.\n @return True if the entity is alive, false if the entity is not alive."] + #[doc = "Test whether an entity is alive.\n Entities are alive after they are created, and become not alive when they are\n deleted. Operations that return alive ids are (amongst others) ecs_new_id,\n ecs_new_low_id and ecs_entity_init. Ids can be made alive with the ecs_ensure\n function.\n\n After an id is deleted it can be recycled. Recycled ids are different from\n the original id in that they have a different generation count. This makes it\n possible for the API to distinguish between the two. An example:\n\n ecs_entity_t e1 = ecs_new_id(world);\n ecs_is_alive(world, e1); // true\n ecs_delete(world, e1);\n ecs_is_alive(world, e1); // false\n\n ecs_entity_t e2 = ecs_new_id(world); // recycles e1\n ecs_is_alive(world, e2); // true\n ecs_is_alive(world, e1); // false\n\n @param world The world.\n @param e The entity.\n @return True if the entity is alive, false if the entity is not alive."] pub fn ecs_is_alive(world: *const ecs_world_t, e: ecs_entity_t) -> bool; } extern "C" { @@ -3108,11 +3181,11 @@ extern "C" { pub fn ecs_has_id(world: *const ecs_world_t, entity: ecs_entity_t, id: ecs_id_t) -> bool; } extern "C" { - #[doc = "Test if an entity owns an id.\n This operation returns true if the entity has the specified id. Other than\n ecs_has_id this operation will not return true if the id is inherited.\n\n @param world The world.\n @param entity The entity.\n @param id The id to test for.\n @return True if the entity has the id, false if not."] + #[doc = "Test if an entity owns an id.\n This operation returns true if the entity has the specified id. The operation\n behaves the same as ecs_has_id, except that it will return false for\n components that are inherited through an IsA relationship.\n\n @param world The world.\n @param entity The entity.\n @param id The id to test for.\n @return True if the entity has the id, false if not."] pub fn ecs_owns_id(world: *const ecs_world_t, entity: ecs_entity_t, id: ecs_id_t) -> bool; } extern "C" { - #[doc = "Get the target of a relationship.\n This will return a target (second element of a pair) of the entity for the\n specified relationship. The index allows for iterating through the targets, if a\n single entity has multiple targets for the same relationship.\n\n If the index is larger than the total number of instances the entity has for\n the relationship, the operation will return 0.\n\n @param world The world.\n @param entity The entity.\n @param rel The relationship between the entity and the target.\n @param index The index of the relationship instance.\n @return The target for the relationship at the specified index."] + #[doc = "Get the target of a relationship.\n This will return a target (second element of a pair) of the entity for the\n specified relationship. The index allows for iterating through the targets,\n if a single entity has multiple targets for the same relationship.\n\n If the index is larger than the total number of instances the entity has for\n the relationship, the operation will return 0.\n\n @param world The world.\n @param entity The entity.\n @param rel The relationship between the entity and the target.\n @param index The index of the relationship instance.\n @return The target for the relationship at the specified index."] pub fn ecs_get_target( world: *const ecs_world_t, entity: ecs_entity_t, @@ -3134,7 +3207,7 @@ extern "C" { ) -> ecs_entity_t; } extern "C" { - #[doc = "Return depth for entity in tree for relationship rel.\n Depth is determined by counting the number of targets encountered while\n traversing up the relationship tree for rel. Only acyclic relationships are\n supported.\n\n @param world The world.\n @param entity The entity.\n @param rel The relationship.\n @return The depth of the entity in the tree."] + #[doc = "Return depth for entity in tree for the specified relationship.\n Depth is determined by counting the number of targets encountered while\n traversing up the relationship tree for rel. Only acyclic relationships are\n supported.\n\n @param world The world.\n @param entity The entity.\n @param rel The relationship.\n @return The depth of the entity in the tree."] pub fn ecs_get_depth(world: *const ecs_world_t, entity: ecs_entity_t, rel: ecs_entity_t) -> i32; } @@ -3219,11 +3292,12 @@ extern "C" { ) -> ecs_entity_t; } extern "C" { - #[doc = "Lookup an entity by its symbol name.\n This looks up an entity by symbol stored in (EcsIdentifier, EcsSymbol). The\n operation does not take into account hierarchies.\n\n This operation can be useful to resolve, for example, a type by its C\n identifier, which does not include the Flecs namespacing."] + #[doc = "Lookup an entity by its symbol name.\n This looks up an entity by symbol stored in (EcsIdentifier, EcsSymbol). The\n operation does not take into account hierarchies.\n\n This operation can be useful to resolve, for example, a type by its C\n identifier, which does not include the Flecs namespacing.\n\n @param world The world.\n @param symbol The symbol.\n @param lookup_as_path If not found as a symbol, lookup as path.\n @param recursive If looking up as path, recursively traverse up the tree.\n @return The entity if found, else 0."] pub fn ecs_lookup_symbol( world: *const ecs_world_t, symbol: *const ::std::os::raw::c_char, lookup_as_path: bool, + recursive: bool, ) -> ecs_entity_t; } extern "C" { @@ -3301,6 +3375,10 @@ extern "C" { desc: *const ecs_component_desc_t, ) -> ecs_entity_t; } +extern "C" { + #[doc = "Get the type for an id.\n This function returnsthe type information for an id. The specified id can be\n any valid id. For the rules on how type information is determined based on\n id, see ecs_get_typeid.\n\n @param world The world.\n @param id The id.\n @return The type information of the id."] + pub fn ecs_get_type_info(world: *const ecs_world_t, id: ecs_id_t) -> *const ecs_type_info_t; +} extern "C" { #[doc = "Register hooks for component.\n Hooks allow for the execution of user code when components are constructed,\n copied, moved, destructed, added, removed or set. Hooks can be assigned as\n as long as a component has not yet been used (added to an entity).\n\n The hooks that are currently set can be accessed with ecs_get_type_info.\n\n @param world The world.\n @param id The component id for which to register the actions\n @param hooks Type that contains the component actions."] pub fn ecs_set_hooks_id( @@ -3325,10 +3403,6 @@ extern "C" { #[doc = "Returns whether specified id is in use.\n This operation returns whether an id is in use in the world. An id is in use\n if it has been added to one or more tables.\n\n @param world The world.\n @param id The id.\n @return Whether the id is in use."] pub fn ecs_id_in_use(world: *const ecs_world_t, id: ecs_id_t) -> bool; } -extern "C" { - #[doc = "Get the type for an id.\n This function returnsthe type information for an id. The specified id can be\n any valid id. For the rules on how type information is determined based on\n id, see ecs_get_typeid.\n\n @param world The world.\n @param id The id.\n @return The type information of the id."] - pub fn ecs_get_type_info(world: *const ecs_world_t, id: ecs_id_t) -> *const ecs_type_info_t; -} extern "C" { #[doc = "Get the type for an id.\n This operation returns the component id for an id, if the id is associated\n with a type. For a regular component with a non-zero size (an entity with the\n EcsComponent component) the operation will return the entity itself.\n\n For an entity that does not have the EcsComponent component, or with an\n EcsComponent value with size 0, the operation will return 0.\n\n For a pair id the operation will return the type associated with the pair, by\n applying the following rules in order:\n - The first pair element is returned if it is a component\n - 0 is returned if the relationship entity has the Tag property\n - The second pair element is returned if it is a component\n - 0 is returned.\n\n @param world The world.\n @param id The id.\n @return The type id of the id."] pub fn ecs_get_typeid(world: *const ecs_world_t, id: ecs_id_t) -> ecs_entity_t; @@ -3394,9 +3468,11 @@ extern "C" { pub fn ecs_term_is_initialized(term: *const ecs_term_t) -> bool; } extern "C" { + #[doc = "Is term matched on $this variable.\n This operation checks whether a term is matched on the $this variable, which\n is the default source for queries.\n\n A term has a $this source when:\n - ecs_term_t::src::id is EcsThis\n - ecs_term_t::src::flags is EcsIsVariable\n\n If ecs_term_t::src is not populated, it will be automatically initialized to\n the $this source for the created query.\n\n @param term The term.\n @return True if term matches $this, false if not."] pub fn ecs_term_match_this(term: *const ecs_term_t) -> bool; } extern "C" { + #[doc = "Is term matched on 0 source.\n This operation checks whether a term is matched on a 0 source. A 0 source is\n a term that isn't matched against anything, and can be used just to pass\n (component) ids to a query iterator.\n\n A term has a 0 source when:\n - ecs_term_t::src::id is 0\n - ecs_term_t::src::flags has EcsIsEntity set\n\n @param term The term.\n @return True if term has 0 source, false if not."] pub fn ecs_term_match_0(term: *const ecs_term_t) -> bool; } extern "C" { @@ -3437,18 +3513,18 @@ extern "C" { ) -> ::std::os::raw::c_int; } extern "C" { - #[doc = "Find index for This variable.\n This operation looks up the index of the This variable. This index can\n be used in operations like ecs_iter_set_var and ecs_iter_get_var.\n\n The operation will return -1 if the variable was not found. This happens when\n a filter only has terms that are not matched on the This variable, like a\n filter that exclusively matches singleton components.\n\n @param filter The rule.\n @return The index of the This variable."] + #[doc = "Find index for $this variable.\n This operation looks up the index of the $this variable. This index can\n be used in operations like ecs_iter_set_var and ecs_iter_get_var.\n\n The operation will return -1 if the variable was not found. This happens when\n a filter only has terms that are not matched on the $this variable, like a\n filter that exclusively matches singleton components.\n\n @param filter The rule.\n @return The index of the $this variable."] pub fn ecs_filter_find_this_var(filter: *const ecs_filter_t) -> i32; } extern "C" { - #[doc = "Convert ter, to string expression.\n Convert term to a string expression. The resulting expression is equivalent\n to the same term, with the exception of And & Or operators."] + #[doc = "Convert term to string expression.\n Convert term to a string expression. The resulting expression is equivalent\n to the same term, with the exception of And & Or operators.\n\n @param world The world.\n @param term The term.\n @return The term converted to a string."] pub fn ecs_term_str( world: *const ecs_world_t, term: *const ecs_term_t, ) -> *mut ::std::os::raw::c_char; } extern "C" { - #[doc = "Convert filter to string expression.\n Convert filter terms to a string expression. The resulting expression can be\n parsed to create the same filter."] + #[doc = "Convert filter to string expression.\n Convert filter terms to a string expression. The resulting expression can be\n parsed to create the same filter.\n\n @param world The world.\n @param filter The filter.\n @return The filter converted to a string."] pub fn ecs_filter_str( world: *const ecs_world_t, filter: *const ecs_filter_t, @@ -3471,15 +3547,15 @@ extern "C" { pub fn ecs_filter_next(it: *mut ecs_iter_t) -> bool; } extern "C" { - #[doc = "Same as ecs_filter_next, but always instanced.\n See instanced property of ecs_filter_desc_t."] + #[doc = "Same as ecs_filter_next, but always instanced.\n See instanced property of ecs_filter_desc_t.\n\n @param it The iterator\n @return True if more data is available, false if not."] pub fn ecs_filter_next_instanced(it: *mut ecs_iter_t) -> bool; } extern "C" { - #[doc = "Move resources of one filter to another."] + #[doc = "Move resources of one filter to another.\n\n @param dst The destination filter.\n @param src The source filter."] pub fn ecs_filter_move(dst: *mut ecs_filter_t, src: *mut ecs_filter_t); } extern "C" { - #[doc = "Copy resources of one filter to another."] + #[doc = "Copy resources of one filter to another.\n\n @param dst The destination filter.\n @param src The source filter."] pub fn ecs_filter_copy(dst: *mut ecs_filter_t, src: *const ecs_filter_t); } extern "C" { @@ -3494,7 +3570,7 @@ extern "C" { pub fn ecs_query_fini(query: *mut ecs_query_t); } extern "C" { - #[doc = "Get filter from a query.\n This operation obtains a pointer to the internally constructed filter\n of the query and can be used to introspect the query terms.\n\n @param query The query."] + #[doc = "Get filter from a query.\n This operation obtains a pointer to the internally constructed filter\n of the query and can be used to introspect the query terms.\n\n @param query The query.\n @return The filter."] pub fn ecs_query_get_filter(query: *const ecs_query_t) -> *const ecs_filter_t; } extern "C" { @@ -3506,7 +3582,7 @@ extern "C" { pub fn ecs_query_next(iter: *mut ecs_iter_t) -> bool; } extern "C" { - #[doc = "Same as ecs_query_next, but always instanced.\n See \"instanced\" property of ecs_filter_desc_t."] + #[doc = "Same as ecs_query_next, but always instanced.\n See \"instanced\" property of ecs_filter_desc_t.\n\n @param iter The iterator.\n @returns True if more data is available, false if not."] pub fn ecs_query_next_instanced(iter: *mut ecs_iter_t) -> bool; } extern "C" { @@ -3563,33 +3639,16 @@ extern "C" { #[doc = "Returns number of entities query matched with.\n This operation iterates all non-empty tables in the query cache to find the\n total number of entities.\n\n @param query The query.\n @return The number of matched entities."] pub fn ecs_query_entity_count(query: *const ecs_query_t) -> i32; } -#[doc = "@defgroup observer Observers\n @brief Functions for working with events and observers.\n @{"] -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct ecs_event_desc_t { - #[doc = "The event id. Only triggers for the specified event will be notified"] - pub event: ecs_entity_t, - #[doc = "Component ids. Only triggers with a matching component id will be\n notified. Observers are guaranteed to get notified once, even if they\n match more than one id."] - pub ids: *const ecs_type_t, - #[doc = "The table for which to notify."] - pub table: *mut ecs_table_t, - #[doc = "Optional 2nd table to notify. This can be used to communicate the\n previous or next table, in case an entity is moved between tables."] - pub other_table: *mut ecs_table_t, - #[doc = "Limit notified entities to ones starting from offset (row) in table"] - pub offset: i32, - #[doc = "Limit number of notified entities to count. offset+count must be less\n than the total number of entities in the table. If left to 0, it will be\n automatically determined by doing ecs_table_count(table) - offset."] - pub count: i32, - #[doc = "Single-entity alternative to setting table / offset / count"] - pub entity: ecs_entity_t, - #[doc = "Optional context. Assigned to iter param member"] - pub param: *const ::std::os::raw::c_void, - #[doc = "Observable (usually the world)"] - pub observable: *mut ecs_poly_t, - #[doc = "Event flags"] - pub flags: ecs_flags32_t, +extern "C" { + #[doc = "Get query ctx.\n Return the value set in ecs_query_desc_t::ctx.\n\n @param query The query.\n @return The context."] + pub fn ecs_query_get_ctx(query: *const ecs_query_t) -> *mut ::std::os::raw::c_void; } extern "C" { - #[doc = "Send event.\n This sends an event to matching triggers & is the mechanism used by flecs\n itself to send OnAdd, OnRemove, etc events.\n\n Applications can use this function to send custom events, where a custom\n event can be any regular entity.\n\n Applications should not send builtin flecs events, as this may violate\n assumptions the code makes about the conditions under which those events are\n sent.\n\n Triggers are invoked synchronously. It is therefore safe to use stack-based\n data as event context, which can be set in the \"param\" member.\n\n To send a notification for a single entity, an application should set the\n following members in the event descriptor:\n\n - table: set to the table of the entity\n - offset: set to the row of the entity in the table\n - count: set to 1\n\n The table & row of the entity can be obtained like this:\n ecs_record_t *r = ecs_record_find(world, e);\n desc.table = r->table;\n desc.offset = ECS_RECORD_TO_ROW(r->row);\n\n @param world The world.\n @param desc Event parameters."] + #[doc = "Get query binding ctx.\n Return the value set in ecs_query_desc_t::binding_ctx.\n\n @param query The query.\n @return The context."] + pub fn ecs_query_get_binding_ctx(query: *const ecs_query_t) -> *mut ::std::os::raw::c_void; +} +extern "C" { + #[doc = "Send event.\n This sends an event to matching triggers & is the mechanism used by flecs\n itself to send OnAdd, OnRemove, etc events.\n\n Applications can use this function to send custom events, where a custom\n event can be any regular entity.\n\n Applications should not send builtin flecs events, as this may violate\n assumptions the code makes about the conditions under which those events are\n sent.\n\n Triggers are invoked synchronously. It is therefore safe to use stack-based\n data as event context, which can be set in the \"param\" member.\n\n @param world The world.\n @param desc Event parameters."] pub fn ecs_emit(world: *mut ecs_world_t, desc: *mut ecs_event_desc_t); } extern "C" { @@ -3604,13 +3663,15 @@ extern "C" { pub fn ecs_observer_default_run_action(it: *mut ecs_iter_t) -> bool; } extern "C" { - pub fn ecs_get_observer_ctx( + #[doc = "Get observer ctx.\n Return the value set in ecs_observer_desc_t::ctx.\n\n @param world The world.\n @param observer The observer.\n @return The context."] + pub fn ecs_observer_get_ctx( world: *const ecs_world_t, observer: ecs_entity_t, ) -> *mut ::std::os::raw::c_void; } extern "C" { - pub fn ecs_get_observer_binding_ctx( + #[doc = "Get observer binding ctx.\n Return the value set in ecs_observer_desc_t::binding_ctx.\n\n @param world The world.\n @param observer The observer.\n @return The context."] + pub fn ecs_observer_get_binding_ctx( world: *const ecs_world_t, observer: ecs_entity_t, ) -> *mut ::std::os::raw::c_void; @@ -3645,7 +3706,7 @@ extern "C" { pub fn ecs_iter_first(it: *mut ecs_iter_t) -> ecs_entity_t; } extern "C" { - #[doc = "Set value for iterator variable.\n This constrains the iterator to return only results for which the variable\n equals the specified value. The default value for all variables is\n EcsWildcard, which means the variable can assume any value.\n\n Example:\n\n // Rule that matches (Eats, *)\n ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){\n .terms = {\n { .first.id = Eats, .second.name = \"_Food\" }\n }\n });\n\n int food_var = ecs_rule_find_var(r, \"Food\");\n\n // Set Food to Apples, so we're only matching (Eats, Apples)\n ecs_iter_t it = ecs_rule_iter(world, r);\n ecs_iter_set_var(&it, food_var, Apples);\n\n while (ecs_rule_next(&it)) {\n for (int i = 0; i < it.count; i ++) {\n // iterate as usual\n }\n }\n\n The variable must be initialized after creating the iterator and before the\n first call to next.\n\n @param it The iterator.\n @param var_id The variable index.\n @param entity The entity variable value."] + #[doc = "Set value for iterator variable.\n This constrains the iterator to return only results for which the variable\n equals the specified value. The default value for all variables is\n EcsWildcard, which means the variable can assume any value.\n\n Example:\n\n // Rule that matches (Eats, *)\n ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){\n .terms = {\n { .first.id = Eats, .second.name = \"$food\" }\n }\n });\n\n int food_var = ecs_rule_find_var(r, \"food\");\n\n // Set Food to Apples, so we're only matching (Eats, Apples)\n ecs_iter_t it = ecs_rule_iter(world, r);\n ecs_iter_set_var(&it, food_var, Apples);\n\n while (ecs_rule_next(&it)) {\n for (int i = 0; i < it.count; i ++) {\n // iterate as usual\n }\n }\n\n The variable must be initialized after creating the iterator and before the\n first call to next.\n\n @param it The iterator.\n @param var_id The variable index.\n @param entity The entity variable value."] pub fn ecs_iter_set_var(it: *mut ecs_iter_t, var_id: i32, entity: ecs_entity_t); } extern "C" { @@ -3661,21 +3722,25 @@ extern "C" { ); } extern "C" { - #[doc = "Get value of iterator variable as entity.\n A variable can be interpreted as entity if it is set to an entity, or if it\n is set to a table range with count 1.\n\n This operation can only be invoked on valid iterators. The variable index\n must be smaller than the total number of variables provided by the iterator\n (as set in ecs_iter_t::variable_count).\n\n @param it The iterator.\n @param var_id The variable index."] + #[doc = "Get value of iterator variable as entity.\n A variable can be interpreted as entity if it is set to an entity, or if it\n is set to a table range with count 1.\n\n This operation can only be invoked on valid iterators. The variable index\n must be smaller than the total number of variables provided by the iterator\n (as set in ecs_iter_t::variable_count).\n\n @param it The iterator.\n @param var_id The variable index.\n @return The variable value."] pub fn ecs_iter_get_var(it: *mut ecs_iter_t, var_id: i32) -> ecs_entity_t; } extern "C" { - #[doc = "Get value of iterator variable as table.\n A variable can be interpreted as table if it is set as table range with\n both offset and count set to 0, or if offset is 0 and count matches the\n number of elements in the table.\n\n This operation can only be invoked on valid iterators. The variable index\n must be smaller than the total number of variables provided by the iterator\n (as set in ecs_iter_t::variable_count).\n\n @param it The iterator.\n @param var_id The variable index."] + #[doc = "Get value of iterator variable as table.\n A variable can be interpreted as table if it is set as table range with\n both offset and count set to 0, or if offset is 0 and count matches the\n number of elements in the table.\n\n This operation can only be invoked on valid iterators. The variable index\n must be smaller than the total number of variables provided by the iterator\n (as set in ecs_iter_t::variable_count).\n\n @param it The iterator.\n @param var_id The variable index.\n @return The variable value."] pub fn ecs_iter_get_var_as_table(it: *mut ecs_iter_t, var_id: i32) -> *mut ecs_table_t; } extern "C" { - #[doc = "Get value of iterator variable as table range.\n A value can be interpreted as table range if it is set as table range, or if\n it is set to an entity with a non-empty type (the entity must have at least\n one component, tag or relationship in its type).\n\n This operation can only be invoked on valid iterators. The variable index\n must be smaller than the total number of variables provided by the iterator\n (as set in ecs_iter_t::variable_count).\n\n @param it The iterator.\n @param var_id The variable index."] + #[doc = "Get value of iterator variable as table range.\n A value can be interpreted as table range if it is set as table range, or if\n it is set to an entity with a non-empty type (the entity must have at least\n one component, tag or relationship in its type).\n\n This operation can only be invoked on valid iterators. The variable index\n must be smaller than the total number of variables provided by the iterator\n (as set in ecs_iter_t::variable_count).\n\n @param it The iterator.\n @param var_id The variable index.\n @return The variable value."] pub fn ecs_iter_get_var_as_range(it: *mut ecs_iter_t, var_id: i32) -> ecs_table_range_t; } extern "C" { #[doc = "Returns whether variable is constrained.\n This operation returns true for variables set by one of the ecs_iter_set_var*\n operations.\n\n A constrained variable is guaranteed not to change values while results are\n being iterated.\n\n @param it The iterator.\n @param var_id The variable index.\n @return Whether the variable is constrained to a specified value."] pub fn ecs_iter_var_is_constrained(it: *mut ecs_iter_t, var_id: i32) -> bool; } +extern "C" { + #[doc = "Convert iterator to string.\n Prints the contents of an iterator to a string. Useful for debugging and/or\n testing the output of an iterator.\n\n The function only converts the currently iterated data to a string. To\n convert all data, the application has to manually call the next function and\n call ecs_iter_str on each result.\n\n @param it The iterator.\n @return A string representing the contents of the iterator."] + pub fn ecs_iter_str(it: *const ecs_iter_t) -> *mut ::std::os::raw::c_char; +} extern "C" { #[doc = "Create a paged iterator.\n Paged iterators limit the results to those starting from 'offset', and will\n return at most 'limit' results.\n\n The iterator must be iterated with ecs_page_next.\n\n A paged iterator acts as a passthrough for data exposed by the parent\n iterator, so that any data provided by the parent will also be provided by\n the paged iterator.\n\n @param it The source iterator.\n @param offset The number of entities to skip.\n @param limit The maximum number of entities to iterate.\n @return A page iterator."] pub fn ecs_page_iter(it: *const ecs_iter_t, offset: i32, limit: i32) -> ecs_iter_t; @@ -3717,7 +3782,7 @@ extern "C" { pub fn ecs_field_id(it: *const ecs_iter_t, index: i32) -> ecs_id_t; } extern "C" { - #[doc = "Return index of matched table column.\n This function only returns column indices for fields that have been matched\n on the the $this variable. Fields matched on other tables will return -1.\n\n @param it The iterator.\n @param index The index of the field in the iterator.\n @return The index of the matched column, -1 if not matched."] + #[doc = "Return index of matched table column.\n This function only returns column indices for fields that have been matched\n on the $this variable. Fields matched on other tables will return -1.\n\n @param it The iterator.\n @param index The index of the field in the iterator.\n @return The index of the matched column, -1 if not matched."] pub fn ecs_field_column_index(it: *const ecs_iter_t, index: i32) -> i32; } extern "C" { @@ -3725,7 +3790,7 @@ extern "C" { pub fn ecs_field_src(it: *const ecs_iter_t, index: i32) -> ecs_entity_t; } extern "C" { - #[doc = "Return field type size.\n Return type size of the data returned by field. Returns 0 if field has no\n data.\n\n @param it The iterator.\n @param index The index of the field in the iterator.\n @return The type size for the field."] + #[doc = "Return field type size.\n Return type size of the field. Returns 0 if the field has no data.\n\n @param it The iterator.\n @param index The index of the field in the iterator.\n @return The type size for the field."] pub fn ecs_field_size(it: *const ecs_iter_t, index: i32) -> usize; } extern "C" { @@ -3733,40 +3798,44 @@ extern "C" { pub fn ecs_field_is_self(it: *const ecs_iter_t, index: i32) -> bool; } extern "C" { - #[doc = "Convert iterator to string.\n Prints the contents of an iterator to a string. Useful for debugging and/or\n testing the output of an iterator.\n\n The function only converts the currently iterated data to a string. To\n convert all data, the application has to manually call the next function and\n call ecs_iter_str on each result.\n\n @param it The iterator.\n @return A string representing the contents of the iterator."] - pub fn ecs_iter_str(it: *const ecs_iter_t) -> *mut ::std::os::raw::c_char; -} -extern "C" { - #[doc = "Get type for table.\n\n @param table The table.\n @return The type of the table."] + #[doc = "Get type for table.\n The table type is a vector that contains all component, tag and pair ids.\n\n @param table The table.\n @return The type of the table."] pub fn ecs_table_get_type(table: *const ecs_table_t) -> *const ecs_type_t; } extern "C" { - #[doc = "Get column from table.\n This operation returns the component array for the provided index.\n\n @param table The table.\n @param index The index of the column (corresponds with element in type).\n @param offset The index of the first row to return (0 for entire column).\n @return The component array, or NULL if the index is not a component."] - pub fn ecs_table_get_column( + #[doc = "Get type index for id.\n This operation returns the index for an id in the table's type.\n\n @param world The world.\n @param table The table.\n @param id The id.\n @return The index of the id in the table type, or -1 if not found."] + pub fn ecs_table_get_type_index( + world: *const ecs_world_t, table: *const ecs_table_t, - index: i32, - offset: i32, - ) -> *mut ::std::os::raw::c_void; -} -extern "C" { - #[doc = "Get column size from table.\n This operation returns the component size for the provided index.\n\n @param table The table.\n @param index The index of the column (corresponds with element in type).\n @return The component size, or 0 if the index is not a component."] - pub fn ecs_table_get_column_size(table: *const ecs_table_t, index: i32) -> usize; + id: ecs_id_t, + ) -> i32; } extern "C" { - #[doc = "Get column index for id.\n This operation returns the index for an id in the table's type.\n\n @param world The world.\n @param table The table.\n @param id The id.\n @return The index of the id in the table type, or -1 if not found."] - pub fn ecs_table_get_index( + #[doc = "Get column index for id.\n This operation returns the column index for an id in the table's type. If the\n id is not a component, the function will return -1.\n\n @param world The world.\n @param table The table.\n @param id The component id.\n @return The column index of the id, or -1 if not found/not a component."] + pub fn ecs_table_get_column_index( world: *const ecs_world_t, table: *const ecs_table_t, id: ecs_id_t, ) -> i32; } extern "C" { - #[doc = "Test if table has id.\n Same as ecs_table_get_index(world, table, id) != -1.\n\n @param world The world.\n @param table The table.\n @param id The id.\n @return True if the table has the id, false if the table doesn't."] - pub fn ecs_table_has_id( - world: *const ecs_world_t, + #[doc = "Return number of columns in table.\n Similar to ecs_table_get_type(table)->count, except that the column count\n only counts the number of components in a table.\n\n @param table The table.\n @return The number of columns in the table."] + pub fn ecs_table_column_count(table: *const ecs_table_t) -> i32; +} +extern "C" { + #[doc = "Convert type index to column index.\n Tables have an array of columns for each component in the table. This array\n does not include elements for tags, which means that the index for a\n component in the table type is not necessarily the same as the index in the\n column array. This operation converts from an index in the table type to an\n index in the column array.\n\n @param table The table.\n @param index The index in the table type.\n @return The index in the table column array."] + pub fn ecs_table_type_to_column_index(table: *const ecs_table_t, index: i32) -> i32; +} +extern "C" { + #[doc = "Convert column index to type index.\n Same as ecs_table_type_to_column_index, but converts from an index in the\n column array to an index in the table type.\n\n @param table The table.\n @param index The column index.\n @return The index in the table type."] + pub fn ecs_table_column_to_type_index(table: *const ecs_table_t, index: i32) -> i32; +} +extern "C" { + #[doc = "Get column from table by column index.\n This operation returns the component array for the provided index.\n\n @param table The table.\n @param index The column index.\n @param offset The index of the first row to return (0 for entire column).\n @return The component array, or NULL if the index is not a component."] + pub fn ecs_table_get_column( table: *const ecs_table_t, - id: ecs_id_t, - ) -> bool; + index: i32, + offset: i32, + ) -> *mut ::std::os::raw::c_void; } extern "C" { #[doc = "Get column from table by component id.\n This operation returns the component array for the provided component id.\n\n @param table The table.\n @param id The component id for the column.\n @param offset The index of the first row to return (0 for entire column).\n @return The component array, or NULL if the index is not a component."] @@ -3778,28 +3847,28 @@ extern "C" { ) -> *mut ::std::os::raw::c_void; } extern "C" { - #[doc = "Return depth for table in tree for relationship rel.\n Depth is determined by counting the number of targets encountered while\n traversing up the relationship tree for rel. Only acyclic relationships are\n supported.\n\n @param world The world.\n @param table The table.\n @param rel The relationship.\n @return The depth of the table in the tree."] - pub fn ecs_table_get_depth( - world: *const ecs_world_t, - table: *const ecs_table_t, - rel: ecs_entity_t, - ) -> i32; -} -extern "C" { - #[doc = "Get storage type for table.\n\n @param table The table.\n @return The storage type of the table (components only)."] - pub fn ecs_table_get_storage_table(table: *const ecs_table_t) -> *mut ecs_table_t; + #[doc = "Get column size from table.\n This operation returns the component size for the provided index.\n\n @param table The table.\n @param index The column index.\n @return The component size, or 0 if the index is not a component."] + pub fn ecs_table_get_column_size(table: *const ecs_table_t, index: i32) -> usize; } extern "C" { - #[doc = "Convert index in table type to index in table storage type."] - pub fn ecs_table_type_to_storage_index(table: *const ecs_table_t, index: i32) -> i32; + #[doc = "Returns the number of records in the table.\n This operation returns the number of records that have been populated through\n the regular (entity) API as well as the number of records that have been\n inserted using the direct access API.\n\n @param table The table.\n @return The number of records in a table."] + pub fn ecs_table_count(table: *const ecs_table_t) -> i32; } extern "C" { - #[doc = "Convert index in table storage type to index in table type."] - pub fn ecs_table_storage_to_type_index(table: *const ecs_table_t, index: i32) -> i32; + #[doc = "Test if table has id.\n Same as ecs_table_get_type_index(world, table, id) != -1.\n\n @param world The world.\n @param table The table.\n @param id The id.\n @return True if the table has the id, false if the table doesn't."] + pub fn ecs_table_has_id( + world: *const ecs_world_t, + table: *const ecs_table_t, + id: ecs_id_t, + ) -> bool; } extern "C" { - #[doc = "Returns the number of records in the table.\n This operation returns the number of records that have been populated through\n the regular (entity) API as well as the number of records that have been\n inserted using the direct access API.\n\n @param table The table.\n @return The number of records in a table."] - pub fn ecs_table_count(table: *const ecs_table_t) -> i32; + #[doc = "Return depth for table in tree for relationship rel.\n Depth is determined by counting the number of targets encountered while\n traversing up the relationship tree for rel. Only acyclic relationships are\n supported.\n\n @param world The world.\n @param table The table.\n @param rel The relationship.\n @return The depth of the table in the tree."] + pub fn ecs_table_get_depth( + world: *const ecs_world_t, + table: *const ecs_table_t, + rel: ecs_entity_t, + ) -> i32; } extern "C" { #[doc = "Get table that has all components of current table plus the specified id.\n If the provided table already has the provided id, the operation will return\n the provided table.\n\n @param world The world.\n @param table The table.\n @param id The id to add.\n @result The resulting table."] @@ -3834,8 +3903,8 @@ extern "C" { pub fn ecs_table_unlock(world: *mut ecs_world_t, table: *mut ecs_table_t); } extern "C" { - #[doc = "Returns whether table is a module or contains module contents\n Returns true for tables that have module contents. Can be used to filter out\n tables that do not contain application data.\n\n @param table The table.\n @return true if table contains module contents, false if not."] - pub fn ecs_table_has_module(table: *mut ecs_table_t) -> bool; + #[doc = "Test table for flags.\n Test if table has all of the provided flags. See\n include/flecs/private/api_flags.h for a list of table flags that can be used\n with this function.\n\n @param table The table.\n @param flags The flags to test for.\n @return Whether the specified flags are set for the table."] + pub fn ecs_table_has_flags(table: *mut ecs_table_t, flags: ecs_flags32_t) -> bool; } extern "C" { #[doc = "Swaps two elements inside the table. This is useful for implementing custom\n table sorting algorithms.\n @param world The world\n @param table The table to swap elements in\n @param row_1 Table element to swap with row_2\n @param row_2 Table element to swap with row_1"] @@ -4012,7 +4081,7 @@ extern "C" { } extern "C" { #[doc = "Tracing"] - pub fn _ecs_deprecated( + pub fn ecs_deprecated_( file: *const ::std::os::raw::c_char, line: i32, msg: *const ::std::os::raw::c_char, @@ -4020,11 +4089,11 @@ extern "C" { } extern "C" { #[doc = "Increase log stack.\n This operation increases the indent_ value of the OS API and can be useful to\n make nested behavior more visible.\n\n @param level The log level."] - pub fn _ecs_log_push(level: i32); + pub fn ecs_log_push_(level: i32); } extern "C" { #[doc = "Decrease log stack.\n This operation decreases the indent_ value of the OS API and can be useful to\n make nested behavior more visible.\n\n @param level The log level."] - pub fn _ecs_log_pop(level: i32); + pub fn ecs_log_pop_(level: i32); } extern "C" { #[doc = "Should current level be logged.\n This operation returns true when the specified log level should be logged\n with the current log level.\n\n @param level The log level to check for.\n @return Whether logging is enabled for the current level."] @@ -4036,7 +4105,7 @@ extern "C" { } extern "C" { #[doc = "Logging functions (do nothing when logging is enabled)"] - pub fn _ecs_print( + pub fn ecs_print_( level: i32, file: *const ::std::os::raw::c_char, line: i32, @@ -4045,7 +4114,7 @@ extern "C" { ); } extern "C" { - pub fn _ecs_printv( + pub fn ecs_printv_( level: ::std::os::raw::c_int, file: *const ::std::os::raw::c_char, line: i32, @@ -4054,7 +4123,7 @@ extern "C" { ); } extern "C" { - pub fn _ecs_log( + pub fn ecs_log_( level: i32, file: *const ::std::os::raw::c_char, line: i32, @@ -4063,7 +4132,7 @@ extern "C" { ); } extern "C" { - pub fn _ecs_logv( + pub fn ecs_logv_( level: ::std::os::raw::c_int, file: *const ::std::os::raw::c_char, line: i32, @@ -4072,7 +4141,7 @@ extern "C" { ); } extern "C" { - pub fn _ecs_abort( + pub fn ecs_abort_( error_code: i32, file: *const ::std::os::raw::c_char, line: i32, @@ -4081,7 +4150,7 @@ extern "C" { ); } extern "C" { - pub fn _ecs_assert( + pub fn ecs_assert_( condition: bool, error_code: i32, condition_str: *const ::std::os::raw::c_char, @@ -4092,7 +4161,7 @@ extern "C" { ) -> bool; } extern "C" { - pub fn _ecs_parser_error( + pub fn ecs_parser_error_( name: *const ::std::os::raw::c_char, expr: *const ::std::os::raw::c_char, column: i64, @@ -4101,7 +4170,7 @@ extern "C" { ); } extern "C" { - pub fn _ecs_parser_errorv( + pub fn ecs_parser_errorv_( name: *const ::std::os::raw::c_char, expr: *const ::std::os::raw::c_char, column: i64, @@ -4221,7 +4290,7 @@ pub const ecs_http_method_t_EcsHttpDelete: ecs_http_method_t = 3; pub const ecs_http_method_t_EcsHttpOptions: ecs_http_method_t = 4; pub const ecs_http_method_t_EcsHttpMethodUnsupported: ecs_http_method_t = 5; #[doc = "Supported request methods"] -pub type ecs_http_method_t = ::std::os::raw::c_int; +pub type ecs_http_method_t = ::std::os::raw::c_uint; #[doc = "A request"] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -4359,7 +4428,7 @@ extern "C" { ) -> *const ::std::os::raw::c_char; } extern "C" { - pub static FLECS__EEcsRest: ecs_entity_t; + pub static FLECS_IDEcsRestID_: ecs_entity_t; } #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -4462,7 +4531,7 @@ pub struct EcsRateFilter { pub time_elapsed: f32, } extern "C" { - #[doc = "Set timer timeout.\n This operation executes any systems associated with the timer after the\n specified timeout value. If the entity contains an existing timer, the\n timeout value will be reset. The timer can be started and stopped with\n ecs_start_timer and ecs_stop_timer.\n\n The timer is synchronous, and is incremented each frame by delta_time.\n\n The tick_source entity will be be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The timer for which to set the timeout (0 to create one).\n @param timeout The timeout value.\n @return The timer entity."] + #[doc = "Set timer timeout.\n This operation executes any systems associated with the timer after the\n specified timeout value. If the entity contains an existing timer, the\n timeout value will be reset. The timer can be started and stopped with\n ecs_start_timer and ecs_stop_timer.\n\n The timer is synchronous, and is incremented each frame by delta_time.\n\n The tick_source entity will be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The timer for which to set the timeout (0 to create one).\n @param timeout The timeout value.\n @return The timer entity."] pub fn ecs_set_timeout( world: *mut ecs_world_t, tick_source: ecs_entity_t, @@ -4470,11 +4539,11 @@ extern "C" { ) -> ecs_entity_t; } extern "C" { - #[doc = "Get current timeout value for the specified timer.\n This operation returns the value set by ecs_set_timeout. If no timer is\n active for this entity, the operation returns 0.\n\n After the timeout expires the EcsTimer component is removed from the entity.\n This means that if ecs_get_timeout is invoked after the timer is expired, the\n operation will return 0.\n\n The timer is synchronous, and is incremented each frame by delta_time.\n\n The tick_source entity will be be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The timer.\n @return The current timeout value, or 0 if no timer is active."] + #[doc = "Get current timeout value for the specified timer.\n This operation returns the value set by ecs_set_timeout. If no timer is\n active for this entity, the operation returns 0.\n\n After the timeout expires the EcsTimer component is removed from the entity.\n This means that if ecs_get_timeout is invoked after the timer is expired, the\n operation will return 0.\n\n The timer is synchronous, and is incremented each frame by delta_time.\n\n The tick_source entity will be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The timer.\n @return The current timeout value, or 0 if no timer is active."] pub fn ecs_get_timeout(world: *const ecs_world_t, tick_source: ecs_entity_t) -> f32; } extern "C" { - #[doc = "Set timer interval.\n This operation will continously invoke systems associated with the timer\n after the interval period expires. If the entity contains an existing timer,\n the interval value will be reset.\n\n The timer is synchronous, and is incremented each frame by delta_time.\n\n The tick_source entity will be be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The timer for which to set the interval (0 to create one).\n @param interval The interval value.\n @return The timer entity."] + #[doc = "Set timer interval.\n This operation will continously invoke systems associated with the timer\n after the interval period expires. If the entity contains an existing timer,\n the interval value will be reset.\n\n The timer is synchronous, and is incremented each frame by delta_time.\n\n The tick_source entity will be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The timer for which to set the interval (0 to create one).\n @param interval The interval value.\n @return The timer entity."] pub fn ecs_set_interval( world: *mut ecs_world_t, tick_source: ecs_entity_t, @@ -4486,15 +4555,23 @@ extern "C" { pub fn ecs_get_interval(world: *const ecs_world_t, tick_source: ecs_entity_t) -> f32; } extern "C" { - #[doc = "Start timer.\n This operation resets the timer and starts it with the specified timeout. The\n entity must have the EcsTimer component (added by ecs_set_timeout and\n ecs_set_interval). If the entity does not have the EcsTimer component this\n operation will assert.\n\n @param world The world.\n @param tick_source The timer to start."] + #[doc = "Start timer.\n This operation resets the timer and starts it with the specified timeout.\n\n @param world The world.\n @param tick_source The timer to start."] pub fn ecs_start_timer(world: *mut ecs_world_t, tick_source: ecs_entity_t); } extern "C" { - #[doc = "Stop timer\n This operation stops a timer from triggering. The entity must have the\n EcsTimer component or this operation will assert.\n\n @param world The world.\n @param tick_source The timer to stop."] + #[doc = "Stop timer\n This operation stops a timer from triggering.\n\n @param world The world.\n @param tick_source The timer to stop."] pub fn ecs_stop_timer(world: *mut ecs_world_t, tick_source: ecs_entity_t); } extern "C" { - #[doc = "Set rate filter.\n This operation initializes a rate filter. Rate filters sample tick sources\n and tick at a configurable multiple. A rate filter is a tick source itself,\n which means that rate filters can be chained.\n\n Rate filters enable deterministic system execution which cannot be achieved\n with interval timers alone. For example, if timer A has interval 2.0 and\n timer B has interval 4.0, it is not guaranteed that B will tick at exactly\n twice the multiple of A. This is partly due to the indeterministic nature of\n timers, and partly due to floating point rounding errors.\n\n Rate filters can be combined with timers (or other rate filters) to ensure\n that a system ticks at an exact multiple of a tick source (which can be\n another system). If a rate filter is created with a rate of 1 it will tick\n at the exact same time as its source.\n\n If no tick source is provided, the rate filter will use the frame tick as\n source, which corresponds with the number of times ecs_progress is called.\n\n The tick_source entity will be be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The rate filter entity (0 to create one).\n @param rate The rate to apply.\n @param source The tick source (0 to use frames)\n @return The filter entity."] + #[doc = "Reset time value of timer to 0.\n This operation resets the timer value to 0.\n\n @param world The world.\n @param tick_source The timer to reset."] + pub fn ecs_reset_timer(world: *mut ecs_world_t, tick_source: ecs_entity_t); +} +extern "C" { + #[doc = "Enable randomizing initial time value of timers.\n Intializes timers with a random time value, which can improve scheduling as\n systems/timers for the same interval don't all happen on the same tick.\n\n @param world The world."] + pub fn ecs_randomize_timers(world: *mut ecs_world_t); +} +extern "C" { + #[doc = "Set rate filter.\n This operation initializes a rate filter. Rate filters sample tick sources\n and tick at a configurable multiple. A rate filter is a tick source itself,\n which means that rate filters can be chained.\n\n Rate filters enable deterministic system execution which cannot be achieved\n with interval timers alone. For example, if timer A has interval 2.0 and\n timer B has interval 4.0, it is not guaranteed that B will tick at exactly\n twice the multiple of A. This is partly due to the indeterministic nature of\n timers, and partly due to floating point rounding errors.\n\n Rate filters can be combined with timers (or other rate filters) to ensure\n that a system ticks at an exact multiple of a tick source (which can be\n another system). If a rate filter is created with a rate of 1 it will tick\n at the exact same time as its source.\n\n If no tick source is provided, the rate filter will use the frame tick as\n source, which corresponds with the number of times ecs_progress is called.\n\n The tick_source entity will be a tick source after this operation. Tick\n sources can be read by getting the EcsTickSource component. If the tick\n source ticked this frame, the 'tick' member will be true. When the tick\n source is a system, the system will tick when the timer ticks.\n\n @param world The world.\n @param tick_source The rate filter entity (0 to create one).\n @param rate The rate to apply.\n @param source The tick source (0 to use frames)\n @return The filter entity."] pub fn ecs_set_rate( world: *mut ecs_world_t, tick_source: ecs_entity_t, @@ -4551,7 +4628,7 @@ extern "C" { pub fn ecs_reset_clock(world: *mut ecs_world_t); } extern "C" { - #[doc = "Run pipeline.\n This will run all systems in the provided pipeline. This operation may be\n invoked from multiple threads, and only when staging is disabled, as the\n pipeline manages staging and, if necessary, synchronization between threads.\n\n If 0 is provided for the pipeline id, the default pipeline will be ran (this\n is either the builtin pipeline or the pipeline set with set_pipeline()).\n\n When using progress() this operation will be invoked automatically for the\n default pipeline (either the builtin pipeline or the pipeline set with\n set_pipeline()). An application may run additional pipelines.\n\n Note: calling this function from an application currently only works in\n single threaded applications with a single stage.\n\n @param world The world.\n @param pipeline The pipeline to run."] + #[doc = "Run pipeline.\n This will run all systems in the provided pipeline. This operation may be\n invoked from multiple threads, and only when staging is disabled, as the\n pipeline manages staging and, if necessary, synchronization between threads.\n\n If 0 is provided for the pipeline id, the default pipeline will be ran (this\n is either the builtin pipeline or the pipeline set with set_pipeline()).\n\n When using progress() this operation will be invoked automatically for the\n default pipeline (either the builtin pipeline or the pipeline set with\n set_pipeline()). An application may run additional pipelines.\n\n @param world The world.\n @param pipeline The pipeline to run."] pub fn ecs_run_pipeline(world: *mut ecs_world_t, pipeline: ecs_entity_t, delta_time: f32); } extern "C" { @@ -4616,7 +4693,7 @@ extern "C" { -> ecs_entity_t; } extern "C" { - #[doc = "Run a specific system manually.\n This operation runs a single system manually. It is an efficient way to\n invoke logic on a set of entities, as manual systems are only matched to\n tables at creation time or after creation time, when a new table is created.\n\n Manual systems are useful to evaluate lists of prematched entities at\n application defined times. Because none of the matching logic is evaluated\n before the system is invoked, manual systems are much more efficient than\n manually obtaining a list of entities and retrieving their components.\n\n An application may pass custom data to a system through the param parameter.\n This data can be accessed by the system through the param member in the\n ecs_iter_t value that is passed to the system callback.\n\n Any system may interrupt execution by setting the interrupted_by member in\n the ecs_iter_t value. This is particularly useful for manual systems, where\n the value of interrupted_by is returned by this operation. This, in\n cominbation with the param argument lets applications use manual systems\n to lookup entities: once the entity has been found its handle is passed to\n interrupted_by, which is then subsequently returned.\n\n @param world The world.\n @param system The system to run.\n @param delta_time The time passed since the last system invocation.\n @param param A user-defined parameter to pass to the system.\n @return handle to last evaluated entity if system was interrupted."] + #[doc = "Run a specific system manually.\n This operation runs a single system manually. It is an efficient way to\n invoke logic on a set of entities, as manual systems are only matched to\n tables at creation time or after creation time, when a new table is created.\n\n Manual systems are useful to evaluate lists of prematched entities at\n application defined times. Because none of the matching logic is evaluated\n before the system is invoked, manual systems are much more efficient than\n manually obtaining a list of entities and retrieving their components.\n\n An application may pass custom data to a system through the param parameter.\n This data can be accessed by the system through the param member in the\n ecs_iter_t value that is passed to the system callback.\n\n Any system may interrupt execution by setting the interrupted_by member in\n the ecs_iter_t value. This is particularly useful for manual systems, where\n the value of interrupted_by is returned by this operation. This, in\n combination with the param argument lets applications use manual systems\n to lookup entities: once the entity has been found its handle is passed to\n interrupted_by, which is then subsequently returned.\n\n @param world The world.\n @param system The system to run.\n @param delta_time The time passed since the last system invocation.\n @param param A user-defined parameter to pass to the system.\n @return handle to last evaluated entity if system was interrupted."] pub fn ecs_run( world: *mut ecs_world_t, system: ecs_entity_t, @@ -4655,14 +4732,14 @@ extern "C" { } extern "C" { #[doc = "Get system context.\n This operation returns the context pointer set for the system. If\n the provided entity is not a system, the function will return NULL.\n\n @param world The world.\n @param system The system from which to obtain the context.\n @return The context."] - pub fn ecs_get_system_ctx( + pub fn ecs_system_get_ctx( world: *const ecs_world_t, system: ecs_entity_t, ) -> *mut ::std::os::raw::c_void; } extern "C" { #[doc = "Get system binding context.\n The binding context is a context typically used to attach any language\n binding specific data that is needed when invoking a callback that is\n implemented in another language.\n\n @param world The world.\n @param system The system from which to obtain the context.\n @return The context."] - pub fn ecs_get_system_binding_ctx( + pub fn ecs_system_get_binding_ctx( world: *const ecs_world_t, system: ecs_entity_t, ) -> *mut ::std::os::raw::c_void; @@ -4911,15 +4988,23 @@ pub struct ecs_system_stats_t { pub time_spent: ecs_metric_t, #[doc = "< Number of times system is invoked"] pub invoke_count: ecs_metric_t, - #[doc = "< Whether system is active (is matched with >0 entities)"] - pub active: ecs_metric_t, - #[doc = "< Whether system is enabled"] - pub enabled: ecs_metric_t, pub last_: i64, #[doc = "< Is system a task"] pub task: bool, pub query: ecs_query_stats_t, } +#[doc = "Statistics for sync point"] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ecs_sync_stats_t { + pub first_: i64, + pub time_spent: ecs_metric_t, + pub commands_enqueued: ecs_metric_t, + pub last_: i64, + pub system_count: i32, + pub multi_threaded: bool, + pub no_readonly: bool, +} #[doc = "Statistics for all systems in a pipeline."] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -4928,6 +5013,8 @@ pub struct ecs_pipeline_stats_t { pub canary_: i8, #[doc = "Vector with system ids of all systems in the pipeline. The systems are\n stored in the order they are executed. Merges are represented by a 0."] pub systems: ecs_vec_t, + #[doc = "Vector with sync point stats"] + pub sync_points: ecs_vec_t, #[doc = "Map with system statistics. For each system in the systems vector, an\n entry in the map exists of type ecs_system_stats_t."] pub system_stats: ecs_map_t, #[doc = "Current position in ringbuffer"] @@ -5078,46 +5165,49 @@ extern "C" { pub fn ecs_metric_copy(m: *mut ecs_metric_t, dst: i32, src: i32); } extern "C" { - pub static mut FLECS__EFlecsMetrics: ecs_entity_t; + pub static mut FLECS_IDFlecsMetricsID_: ecs_entity_t; } extern "C" { pub static mut EcsMetric: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMetric: ecs_entity_t; + pub static mut FLECS_IDEcsMetricID_: ecs_entity_t; } extern "C" { pub static mut EcsCounter: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsCounter: ecs_entity_t; + pub static mut FLECS_IDEcsCounterID_: ecs_entity_t; } extern "C" { pub static mut EcsCounterIncrement: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsCounterIncrement: ecs_entity_t; + pub static mut FLECS_IDEcsCounterIncrementID_: ecs_entity_t; } extern "C" { pub static mut EcsCounterId: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsCounterId: ecs_entity_t; + pub static mut FLECS_IDEcsCounterIdID_: ecs_entity_t; } extern "C" { pub static mut EcsGauge: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGauge: ecs_entity_t; + pub static mut FLECS_IDEcsGaugeID_: ecs_entity_t; +} +extern "C" { + pub static mut EcsMetricInstance: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMetricInstance: ecs_entity_t; + pub static mut FLECS_IDEcsMetricInstanceID_: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMetricValue: ecs_entity_t; + pub static mut FLECS_IDEcsMetricValueID_: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMetricSource: ecs_entity_t; + pub static mut FLECS_IDEcsMetricSourceID_: ecs_entity_t; } #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -5137,6 +5227,8 @@ pub struct ecs_metric_desc_t { pub entity: ecs_entity_t, #[doc = "Entity associated with member that stores metric value. Must not be set\n at the same time as id. Cannot be combined with EcsCounterId."] pub member: ecs_entity_t, + #[doc = "Member dot expression. Can be used instead of member and supports nested\n members. Must be set together with id and should not be set at the same\n time as member."] + pub dotmember: *const ::std::os::raw::c_char, #[doc = "Tracks whether entities have the specified component id. Must not be set\n at the same time as member."] pub id: ecs_id_t, #[doc = "If id is a (R, *) wildcard and relationship R has the OneOf property,\n setting this value to true will track individual targets.\n If the kind is EcsCountId and the id is a (R, *) wildcard, this value\n will create a metric per target."] @@ -5156,40 +5248,43 @@ extern "C" { pub fn FlecsMetricsImport(world: *mut ecs_world_t); } extern "C" { - pub static mut FLECS__EFlecsAlerts: ecs_entity_t; + pub static mut FLECS_IDFlecsAlertsID_: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAlert: ecs_entity_t; + pub static mut FLECS_IDEcsAlertID_: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAlertInstance: ecs_entity_t; + pub static mut FLECS_IDEcsAlertInstanceID_: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAlertsActive: ecs_entity_t; + pub static mut FLECS_IDEcsAlertsActiveID_: ecs_entity_t; +} +extern "C" { + pub static mut FLECS_IDEcsAlertTimeoutID_: ecs_entity_t; } extern "C" { pub static mut EcsAlertInfo: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAlertInfo: ecs_entity_t; + pub static mut FLECS_IDEcsAlertInfoID_: ecs_entity_t; } extern "C" { pub static mut EcsAlertWarning: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAlertWarning: ecs_entity_t; + pub static mut FLECS_IDEcsAlertWarningID_: ecs_entity_t; } extern "C" { pub static mut EcsAlertError: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAlertError: ecs_entity_t; + pub static mut FLECS_IDEcsAlertErrorID_: ecs_entity_t; } extern "C" { pub static mut EcsAlertCritical: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAlertCritical: ecs_entity_t; + pub static mut FLECS_IDEcsAlertCriticalID_: ecs_entity_t; } #[doc = "Alert information. Added to each alert instance"] #[repr(C)] @@ -5201,10 +5296,25 @@ pub struct EcsAlertInstance { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsAlertsActive { + pub info_count: i32, + pub warning_count: i32, + pub error_count: i32, pub alerts: ecs_map_t, } #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct ecs_alert_severity_filter_t { + #[doc = "Severity kind"] + pub severity: ecs_entity_t, + #[doc = "Component to match"] + pub with: ecs_id_t, + #[doc = "Variable to match component on. Do not include the\n '$' character. Leave to NULL for $this."] + pub var: *const ::std::os::raw::c_char, + #[doc = "Index of variable in filter (do not set)"] + pub _var_index: i32, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct ecs_alert_desc_t { pub _canary: i32, #[doc = "Entity associated with alert"] @@ -5213,10 +5323,22 @@ pub struct ecs_alert_desc_t { pub filter: ecs_filter_desc_t, #[doc = "Template for alert message. This string is used to generate the alert\n message and may refer to variables in the query result. The format for\n the template expressions is as specified by ecs_interpolate_string.\n\n Examples:\n \"$this has Position but not Velocity\"\n \"$this has a parent entity $parent without Position\""] pub message: *const ::std::os::raw::c_char, - #[doc = "Description of metric. Will only be set if FLECS_DOC addon is enabled"] + #[doc = "User friendly name. Will only be set if FLECS_DOC addon is enabled."] + pub doc_name: *const ::std::os::raw::c_char, + #[doc = "Description of alert. Will only be set if FLECS_DOC addon is enabled"] pub brief: *const ::std::os::raw::c_char, #[doc = "Metric kind. Must be EcsAlertInfo, EcsAlertWarning, EcsAlertError or\n EcsAlertCritical. Defaults to EcsAlertError."] pub severity: ecs_entity_t, + #[doc = "Severity filters can be used to assign different severities to the same\n alert. This prevents having to create multiple alerts, and allows\n entities to transition between severities without resetting the\n alert duration (optional)."] + pub severity_filters: [ecs_alert_severity_filter_t; 4usize], + #[doc = "The retain period specifies how long an alert must be inactive before it\n is cleared. This makes it easier to track noisy alerts. While an alert is\n inactive its duration won't increase.\n When the retain period is 0, the alert will clear immediately after it no\n longer matches the alert query."] + pub retain_period: f32, + #[doc = "Alert when member value is out of range. Uses the warning/error ranges\n assigned to the member in the MemberRanges component (optional)."] + pub member: ecs_entity_t, + #[doc = "(Component) id of member to monitor. If left to 0 this will be set to\n the parent entity of the member (optional)."] + pub id: ecs_id_t, + #[doc = "Variable from which to fetch the member (optional). When left to NULL\n 'id' will be obtained from $this."] + pub var: *const ::std::os::raw::c_char, } extern "C" { #[doc = "Create a new alert.\n An alert is a query that is evaluated periodically and creates alert\n instances for each entity that matches the query. Alerts can be used to\n automate detection of errors in an application.\n\n Alerts are automatically cleared when a query is no longer true for an alert\n instance. At most one alert instance will be created per matched entity.\n\n Alert instances have three components:\n - AlertInstance: contains the alert message for the instance\n - MetricSource: contains the entity that triggered the alert\n - MetricValue: contains how long the alert has been active\n\n Alerts reuse components from the metrics addon so that alert instances can be\n tracked and discovered as metrics. Just like metrics, alert instances are\n created as children of the alert.\n\n When an entity has active alerts, it will have the EcsAlertsActive component\n which contains a map with active alerts for the entity. This component\n will be automatically removed once all alerts are cleared for the entity.\n\n @param world The world.\n @param desc Alert description.\n @return The alert entity."] @@ -5243,13 +5365,16 @@ extern "C" { pub fn FlecsAlertsImport(world: *mut ecs_world_t); } extern "C" { - pub static mut FLECS__EFlecsMonitor: ecs_entity_t; + pub static mut FLECS_IDFlecsMonitorID_: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsWorldStats: ecs_entity_t; + pub static mut FLECS_IDEcsWorldStatsID_: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPipelineStats: ecs_entity_t; + pub static mut FLECS_IDEcsWorldSummaryID_: ecs_entity_t; +} +extern "C" { + pub static mut FLECS_IDEcsPipelineStatsID_: ecs_entity_t; } extern "C" { pub static mut EcsPeriod1s: ecs_entity_t; @@ -5284,6 +5409,24 @@ pub struct EcsPipelineStats { pub hdr: EcsStatsHeader, pub stats: ecs_pipeline_stats_t, } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct EcsWorldSummary { + #[doc = "< Target FPS"] + pub target_fps: f64, + #[doc = "< Total time spent processing a frame"] + pub frame_time_total: f64, + #[doc = "< Total time spent in systems"] + pub system_time_total: f64, + #[doc = "< Total time spent in merges"] + pub merge_time_total: f64, + #[doc = "< Time spent processing a frame"] + pub frame_time_last: f64, + #[doc = "< Time spent in systems"] + pub system_time_last: f64, + #[doc = "< Time spent in merges"] + pub merge_time_last: f64, +} extern "C" { #[doc = "Module import"] pub fn FlecsMonitorImport(world: *mut ecs_world_t); @@ -5293,7 +5436,7 @@ extern "C" { pub fn FlecsCoreDocImport(world: *mut ecs_world_t); } extern "C" { - pub static FLECS__EEcsDocDescription: ecs_entity_t; + pub static FLECS_IDEcsDocDescriptionID_: ecs_entity_t; } extern "C" { pub static EcsDocBrief: ecs_entity_t; @@ -5395,10 +5538,11 @@ extern "C" { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_from_json_desc_t { - #[doc = "Name of expression (used for logging)"] + #[doc = "< Name of expression (used for logging)"] pub name: *const ::std::os::raw::c_char, - #[doc = "Full expression (used for logging)"] + #[doc = "< Full expression (used for logging)"] pub expr: *const ::std::os::raw::c_char, + #[doc = "Callback that allows for specifying a custom lookup function. The\n default behavior uses ecs_lookup_fullpath"] pub lookup_action: ::std::option::Option< unsafe extern "C" fn( arg1: *const ecs_world_t, @@ -5492,8 +5636,6 @@ extern "C" { pub struct ecs_entity_to_json_desc_t { #[doc = "< Serialize full pathname"] pub serialize_path: bool, - #[doc = "< Serialize 'meta' ids (Name, ChildOf, etc)"] - pub serialize_meta_ids: bool, #[doc = "< Serialize doc name"] pub serialize_label: bool, #[doc = "< Serialize brief doc description"] @@ -5502,6 +5644,8 @@ pub struct ecs_entity_to_json_desc_t { pub serialize_link: bool, #[doc = "< Serialize doc color"] pub serialize_color: bool, + #[doc = "< Serialize (component) ids"] + pub serialize_ids: bool, #[doc = "< Serialize labels of (component) ids"] pub serialize_id_labels: bool, #[doc = "< Serialize base components"] @@ -5516,6 +5660,10 @@ pub struct ecs_entity_to_json_desc_t { pub serialize_type_info: bool, #[doc = "< Serialize active alerts for entity"] pub serialize_alerts: bool, + #[doc = "< Serialize references (incoming edges) for relationship"] + pub serialize_refs: ecs_entity_t, + #[doc = "< Serialize which queries entity matches with"] + pub serialize_matches: bool, } extern "C" { #[doc = "Serialize entity into JSON string.\n This creates a JSON object with the entity's (path) name, which components\n and tags the entity has, and the component values.\n\n The operation may fail if the entity contains components with invalid values.\n\n @param world The world.\n @param entity The entity to serialize to JSON.\n @return A JSON string with the serialized entity data, or NULL if failed."] @@ -5538,10 +5686,14 @@ extern "C" { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_iter_to_json_desc_t { - #[doc = "< Serialize term (query) component ids"] + #[doc = "< Serialize query term component ids"] pub serialize_term_ids: bool, + #[doc = "< Serialize query term component id labels"] + pub serialize_term_labels: bool, #[doc = "< Serialize actual (matched) component ids"] pub serialize_ids: bool, + #[doc = "< Serialize actual (matched) component id labels"] + pub serialize_id_labels: bool, #[doc = "< Serialize sources"] pub serialize_sources: bool, #[doc = "< Serialize variables"] @@ -5550,6 +5702,8 @@ pub struct ecs_iter_to_json_desc_t { pub serialize_is_set: bool, #[doc = "< Serialize component values"] pub serialize_values: bool, + #[doc = "< Serialize component values"] + pub serialize_private: bool, #[doc = "< Serialize entities (for This terms)"] pub serialize_entities: bool, #[doc = "< Serialize doc name for entities"] @@ -5592,9 +5746,9 @@ extern "C" { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_world_to_json_desc_t { - #[doc = "Exclude flecs modules & contents"] + #[doc = "< Exclude flecs modules & contents"] pub serialize_builtin: bool, - #[doc = "Exclude modules & contents"] + #[doc = "< Exclude modules & contents"] pub serialize_modules: bool, } extern "C" { @@ -5616,661 +5770,661 @@ extern "C" { pub static mut EcsUnitPrefixes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsUnitPrefixes: ecs_entity_t; + pub static mut FLECS_IDEcsUnitPrefixesID_: ecs_entity_t; } extern "C" { pub static mut EcsYocto: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsYocto: ecs_entity_t; + pub static mut FLECS_IDEcsYoctoID_: ecs_entity_t; } extern "C" { pub static mut EcsZepto: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsZepto: ecs_entity_t; + pub static mut FLECS_IDEcsZeptoID_: ecs_entity_t; } extern "C" { pub static mut EcsAtto: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAtto: ecs_entity_t; + pub static mut FLECS_IDEcsAttoID_: ecs_entity_t; } extern "C" { pub static mut EcsFemto: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsFemto: ecs_entity_t; + pub static mut FLECS_IDEcsFemtoID_: ecs_entity_t; } extern "C" { pub static mut EcsPico: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPico: ecs_entity_t; + pub static mut FLECS_IDEcsPicoID_: ecs_entity_t; } extern "C" { pub static mut EcsNano: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsNano: ecs_entity_t; + pub static mut FLECS_IDEcsNanoID_: ecs_entity_t; } extern "C" { pub static mut EcsMicro: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMicro: ecs_entity_t; + pub static mut FLECS_IDEcsMicroID_: ecs_entity_t; } extern "C" { pub static mut EcsMilli: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMilli: ecs_entity_t; + pub static mut FLECS_IDEcsMilliID_: ecs_entity_t; } extern "C" { pub static mut EcsCenti: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsCenti: ecs_entity_t; + pub static mut FLECS_IDEcsCentiID_: ecs_entity_t; } extern "C" { pub static mut EcsDeci: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDeci: ecs_entity_t; + pub static mut FLECS_IDEcsDeciID_: ecs_entity_t; } extern "C" { pub static mut EcsDeca: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDeca: ecs_entity_t; + pub static mut FLECS_IDEcsDecaID_: ecs_entity_t; } extern "C" { pub static mut EcsHecto: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsHecto: ecs_entity_t; + pub static mut FLECS_IDEcsHectoID_: ecs_entity_t; } extern "C" { pub static mut EcsKilo: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKilo: ecs_entity_t; + pub static mut FLECS_IDEcsKiloID_: ecs_entity_t; } extern "C" { pub static mut EcsMega: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMega: ecs_entity_t; + pub static mut FLECS_IDEcsMegaID_: ecs_entity_t; } extern "C" { pub static mut EcsGiga: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGiga: ecs_entity_t; + pub static mut FLECS_IDEcsGigaID_: ecs_entity_t; } extern "C" { pub static mut EcsTera: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsTera: ecs_entity_t; + pub static mut FLECS_IDEcsTeraID_: ecs_entity_t; } extern "C" { pub static mut EcsPeta: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPeta: ecs_entity_t; + pub static mut FLECS_IDEcsPetaID_: ecs_entity_t; } extern "C" { pub static mut EcsExa: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsExa: ecs_entity_t; + pub static mut FLECS_IDEcsExaID_: ecs_entity_t; } extern "C" { pub static mut EcsZetta: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsZetta: ecs_entity_t; + pub static mut FLECS_IDEcsZettaID_: ecs_entity_t; } extern "C" { pub static mut EcsYotta: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsYotta: ecs_entity_t; + pub static mut FLECS_IDEcsYottaID_: ecs_entity_t; } extern "C" { pub static mut EcsKibi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKibi: ecs_entity_t; + pub static mut FLECS_IDEcsKibiID_: ecs_entity_t; } extern "C" { pub static mut EcsMebi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMebi: ecs_entity_t; + pub static mut FLECS_IDEcsMebiID_: ecs_entity_t; } extern "C" { pub static mut EcsGibi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGibi: ecs_entity_t; + pub static mut FLECS_IDEcsGibiID_: ecs_entity_t; } extern "C" { pub static mut EcsTebi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsTebi: ecs_entity_t; + pub static mut FLECS_IDEcsTebiID_: ecs_entity_t; } extern "C" { pub static mut EcsPebi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPebi: ecs_entity_t; + pub static mut FLECS_IDEcsPebiID_: ecs_entity_t; } extern "C" { pub static mut EcsExbi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsExbi: ecs_entity_t; + pub static mut FLECS_IDEcsExbiID_: ecs_entity_t; } extern "C" { pub static mut EcsZebi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsZebi: ecs_entity_t; + pub static mut FLECS_IDEcsZebiID_: ecs_entity_t; } extern "C" { pub static mut EcsYobi: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsYobi: ecs_entity_t; + pub static mut FLECS_IDEcsYobiID_: ecs_entity_t; } extern "C" { pub static mut EcsDuration: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDuration: ecs_entity_t; + pub static mut FLECS_IDEcsDurationID_: ecs_entity_t; } extern "C" { pub static mut EcsPicoSeconds: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPicoSeconds: ecs_entity_t; + pub static mut FLECS_IDEcsPicoSecondsID_: ecs_entity_t; } extern "C" { pub static mut EcsNanoSeconds: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsNanoSeconds: ecs_entity_t; + pub static mut FLECS_IDEcsNanoSecondsID_: ecs_entity_t; } extern "C" { pub static mut EcsMicroSeconds: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMicroSeconds: ecs_entity_t; + pub static mut FLECS_IDEcsMicroSecondsID_: ecs_entity_t; } extern "C" { pub static mut EcsMilliSeconds: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMilliSeconds: ecs_entity_t; + pub static mut FLECS_IDEcsMilliSecondsID_: ecs_entity_t; } extern "C" { pub static mut EcsSeconds: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsSeconds: ecs_entity_t; + pub static mut FLECS_IDEcsSecondsID_: ecs_entity_t; } extern "C" { pub static mut EcsMinutes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMinutes: ecs_entity_t; + pub static mut FLECS_IDEcsMinutesID_: ecs_entity_t; } extern "C" { pub static mut EcsHours: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsHours: ecs_entity_t; + pub static mut FLECS_IDEcsHoursID_: ecs_entity_t; } extern "C" { pub static mut EcsDays: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDays: ecs_entity_t; + pub static mut FLECS_IDEcsDaysID_: ecs_entity_t; } extern "C" { pub static mut EcsTime: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsTime: ecs_entity_t; + pub static mut FLECS_IDEcsTimeID_: ecs_entity_t; } extern "C" { pub static mut EcsDate: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDate: ecs_entity_t; + pub static mut FLECS_IDEcsDateID_: ecs_entity_t; } extern "C" { pub static mut EcsMass: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMass: ecs_entity_t; + pub static mut FLECS_IDEcsMassID_: ecs_entity_t; } extern "C" { pub static mut EcsGrams: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGrams: ecs_entity_t; + pub static mut FLECS_IDEcsGramsID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloGrams: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloGrams: ecs_entity_t; + pub static mut FLECS_IDEcsKiloGramsID_: ecs_entity_t; } extern "C" { pub static mut EcsElectricCurrent: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsElectricCurrent: ecs_entity_t; + pub static mut FLECS_IDEcsElectricCurrentID_: ecs_entity_t; } extern "C" { pub static mut EcsAmpere: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAmpere: ecs_entity_t; + pub static mut FLECS_IDEcsAmpereID_: ecs_entity_t; } extern "C" { pub static mut EcsAmount: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAmount: ecs_entity_t; + pub static mut FLECS_IDEcsAmountID_: ecs_entity_t; } extern "C" { pub static mut EcsMole: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMole: ecs_entity_t; + pub static mut FLECS_IDEcsMoleID_: ecs_entity_t; } extern "C" { pub static mut EcsLuminousIntensity: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsLuminousIntensity: ecs_entity_t; + pub static mut FLECS_IDEcsLuminousIntensityID_: ecs_entity_t; } extern "C" { pub static mut EcsCandela: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsCandela: ecs_entity_t; + pub static mut FLECS_IDEcsCandelaID_: ecs_entity_t; } extern "C" { pub static mut EcsForce: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsForce: ecs_entity_t; + pub static mut FLECS_IDEcsForceID_: ecs_entity_t; } extern "C" { pub static mut EcsNewton: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsNewton: ecs_entity_t; + pub static mut FLECS_IDEcsNewtonID_: ecs_entity_t; } extern "C" { pub static mut EcsLength: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsLength: ecs_entity_t; + pub static mut FLECS_IDEcsLengthID_: ecs_entity_t; } extern "C" { pub static mut EcsMeters: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMeters: ecs_entity_t; + pub static mut FLECS_IDEcsMetersID_: ecs_entity_t; } extern "C" { pub static mut EcsPicoMeters: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPicoMeters: ecs_entity_t; + pub static mut FLECS_IDEcsPicoMetersID_: ecs_entity_t; } extern "C" { pub static mut EcsNanoMeters: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsNanoMeters: ecs_entity_t; + pub static mut FLECS_IDEcsNanoMetersID_: ecs_entity_t; } extern "C" { pub static mut EcsMicroMeters: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMicroMeters: ecs_entity_t; + pub static mut FLECS_IDEcsMicroMetersID_: ecs_entity_t; } extern "C" { pub static mut EcsMilliMeters: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMilliMeters: ecs_entity_t; + pub static mut FLECS_IDEcsMilliMetersID_: ecs_entity_t; } extern "C" { pub static mut EcsCentiMeters: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsCentiMeters: ecs_entity_t; + pub static mut FLECS_IDEcsCentiMetersID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloMeters: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloMeters: ecs_entity_t; + pub static mut FLECS_IDEcsKiloMetersID_: ecs_entity_t; } extern "C" { pub static mut EcsMiles: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMiles: ecs_entity_t; + pub static mut FLECS_IDEcsMilesID_: ecs_entity_t; } extern "C" { pub static mut EcsPixels: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPixels: ecs_entity_t; + pub static mut FLECS_IDEcsPixelsID_: ecs_entity_t; } extern "C" { pub static mut EcsPressure: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPressure: ecs_entity_t; + pub static mut FLECS_IDEcsPressureID_: ecs_entity_t; } extern "C" { pub static mut EcsPascal: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPascal: ecs_entity_t; + pub static mut FLECS_IDEcsPascalID_: ecs_entity_t; } extern "C" { pub static mut EcsBar: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsBar: ecs_entity_t; + pub static mut FLECS_IDEcsBarID_: ecs_entity_t; } extern "C" { pub static mut EcsSpeed: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsSpeed: ecs_entity_t; + pub static mut FLECS_IDEcsSpeedID_: ecs_entity_t; } extern "C" { pub static mut EcsMetersPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMetersPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsMetersPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloMetersPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloMetersPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsKiloMetersPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloMetersPerHour: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloMetersPerHour: ecs_entity_t; + pub static mut FLECS_IDEcsKiloMetersPerHourID_: ecs_entity_t; } extern "C" { pub static mut EcsMilesPerHour: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMilesPerHour: ecs_entity_t; + pub static mut FLECS_IDEcsMilesPerHourID_: ecs_entity_t; } extern "C" { pub static mut EcsTemperature: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsTemperature: ecs_entity_t; + pub static mut FLECS_IDEcsTemperatureID_: ecs_entity_t; } extern "C" { pub static mut EcsKelvin: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKelvin: ecs_entity_t; + pub static mut FLECS_IDEcsKelvinID_: ecs_entity_t; } extern "C" { pub static mut EcsCelsius: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsCelsius: ecs_entity_t; + pub static mut FLECS_IDEcsCelsiusID_: ecs_entity_t; } extern "C" { pub static mut EcsFahrenheit: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsFahrenheit: ecs_entity_t; + pub static mut FLECS_IDEcsFahrenheitID_: ecs_entity_t; } extern "C" { pub static mut EcsData: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsData: ecs_entity_t; + pub static mut FLECS_IDEcsDataID_: ecs_entity_t; } extern "C" { pub static mut EcsBits: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsBits: ecs_entity_t; + pub static mut FLECS_IDEcsBitsID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloBits: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloBits: ecs_entity_t; + pub static mut FLECS_IDEcsKiloBitsID_: ecs_entity_t; } extern "C" { pub static mut EcsMegaBits: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMegaBits: ecs_entity_t; + pub static mut FLECS_IDEcsMegaBitsID_: ecs_entity_t; } extern "C" { pub static mut EcsGigaBits: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGigaBits: ecs_entity_t; + pub static mut FLECS_IDEcsGigaBitsID_: ecs_entity_t; } extern "C" { pub static mut EcsBytes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsBytes: ecs_entity_t; + pub static mut FLECS_IDEcsBytesID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloBytes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloBytes: ecs_entity_t; + pub static mut FLECS_IDEcsKiloBytesID_: ecs_entity_t; } extern "C" { pub static mut EcsMegaBytes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMegaBytes: ecs_entity_t; + pub static mut FLECS_IDEcsMegaBytesID_: ecs_entity_t; } extern "C" { pub static mut EcsGigaBytes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGigaBytes: ecs_entity_t; + pub static mut FLECS_IDEcsGigaBytesID_: ecs_entity_t; } extern "C" { pub static mut EcsKibiBytes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKibiBytes: ecs_entity_t; + pub static mut FLECS_IDEcsKibiBytesID_: ecs_entity_t; } extern "C" { pub static mut EcsMebiBytes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMebiBytes: ecs_entity_t; + pub static mut FLECS_IDEcsMebiBytesID_: ecs_entity_t; } extern "C" { pub static mut EcsGibiBytes: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGibiBytes: ecs_entity_t; + pub static mut FLECS_IDEcsGibiBytesID_: ecs_entity_t; } extern "C" { pub static mut EcsDataRate: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDataRate: ecs_entity_t; + pub static mut FLECS_IDEcsDataRateID_: ecs_entity_t; } extern "C" { pub static mut EcsBitsPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsBitsPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsBitsPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloBitsPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloBitsPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsKiloBitsPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsMegaBitsPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMegaBitsPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsMegaBitsPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsGigaBitsPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGigaBitsPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsGigaBitsPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsBytesPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsBytesPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsBytesPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloBytesPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloBytesPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsKiloBytesPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsMegaBytesPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMegaBytesPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsMegaBytesPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsGigaBytesPerSecond: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGigaBytesPerSecond: ecs_entity_t; + pub static mut FLECS_IDEcsGigaBytesPerSecondID_: ecs_entity_t; } extern "C" { pub static mut EcsAngle: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAngle: ecs_entity_t; + pub static mut FLECS_IDEcsAngleID_: ecs_entity_t; } extern "C" { pub static mut EcsRadians: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsRadians: ecs_entity_t; + pub static mut FLECS_IDEcsRadiansID_: ecs_entity_t; } extern "C" { pub static mut EcsDegrees: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDegrees: ecs_entity_t; + pub static mut FLECS_IDEcsDegreesID_: ecs_entity_t; } extern "C" { pub static mut EcsFrequency: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsFrequency: ecs_entity_t; + pub static mut FLECS_IDEcsFrequencyID_: ecs_entity_t; } extern "C" { pub static mut EcsHertz: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsHertz: ecs_entity_t; + pub static mut FLECS_IDEcsHertzID_: ecs_entity_t; } extern "C" { pub static mut EcsKiloHertz: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsKiloHertz: ecs_entity_t; + pub static mut FLECS_IDEcsKiloHertzID_: ecs_entity_t; } extern "C" { pub static mut EcsMegaHertz: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsMegaHertz: ecs_entity_t; + pub static mut FLECS_IDEcsMegaHertzID_: ecs_entity_t; } extern "C" { pub static mut EcsGigaHertz: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsGigaHertz: ecs_entity_t; + pub static mut FLECS_IDEcsGigaHertzID_: ecs_entity_t; } extern "C" { pub static mut EcsUri: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsUri: ecs_entity_t; + pub static mut FLECS_IDEcsUriID_: ecs_entity_t; } extern "C" { pub static mut EcsUriHyperlink: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsUriHyperlink: ecs_entity_t; + pub static mut FLECS_IDEcsUriHyperlinkID_: ecs_entity_t; } extern "C" { pub static mut EcsUriImage: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsUriImage: ecs_entity_t; + pub static mut FLECS_IDEcsUriImageID_: ecs_entity_t; } extern "C" { pub static mut EcsUriFile: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsUriFile: ecs_entity_t; + pub static mut FLECS_IDEcsUriFileID_: ecs_entity_t; } extern "C" { pub static mut EcsAcceleration: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsAcceleration: ecs_entity_t; + pub static mut FLECS_IDEcsAccelerationID_: ecs_entity_t; } extern "C" { pub static mut EcsPercentage: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsPercentage: ecs_entity_t; + pub static mut FLECS_IDEcsPercentageID_: ecs_entity_t; } extern "C" { pub static mut EcsBel: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsBel: ecs_entity_t; + pub static mut FLECS_IDEcsBelID_: ecs_entity_t; } extern "C" { pub static mut EcsDeciBel: ecs_entity_t; } extern "C" { - pub static mut FLECS__EEcsDeciBel: ecs_entity_t; + pub static mut FLECS_IDEcsDeciBelID_: ecs_entity_t; } extern "C" { #[doc = "Module"] @@ -6294,40 +6448,43 @@ pub type ecs_f32_t = f32; pub type ecs_f64_t = f64; pub type ecs_string_t = *mut ::std::os::raw::c_char; extern "C" { - pub static FLECS__EEcsMetaType: ecs_entity_t; + pub static FLECS_IDEcsMetaTypeID_: ecs_entity_t; +} +extern "C" { + pub static FLECS_IDEcsMetaTypeSerializedID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsMetaTypeSerialized: ecs_entity_t; + pub static FLECS_IDEcsPrimitiveID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsPrimitive: ecs_entity_t; + pub static FLECS_IDEcsEnumID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsEnum: ecs_entity_t; + pub static FLECS_IDEcsBitmaskID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsBitmask: ecs_entity_t; + pub static FLECS_IDEcsMemberID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsMember: ecs_entity_t; + pub static FLECS_IDEcsMemberRangesID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsStruct: ecs_entity_t; + pub static FLECS_IDEcsStructID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsArray: ecs_entity_t; + pub static FLECS_IDEcsArrayID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsVector: ecs_entity_t; + pub static FLECS_IDEcsVectorID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsOpaque: ecs_entity_t; + pub static FLECS_IDEcsOpaqueID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsUnit: ecs_entity_t; + pub static FLECS_IDEcsUnitID_: ecs_entity_t; } extern "C" { - pub static FLECS__EEcsUnitPrefix: ecs_entity_t; + pub static FLECS_IDEcsUnitPrefixID_: ecs_entity_t; } extern "C" { pub static EcsConstant: ecs_entity_t; @@ -6336,55 +6493,55 @@ extern "C" { pub static EcsQuantity: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_bool_t: ecs_entity_t; + pub static FLECS_IDecs_bool_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_char_t: ecs_entity_t; + pub static FLECS_IDecs_char_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_byte_t: ecs_entity_t; + pub static FLECS_IDecs_byte_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_u8_t: ecs_entity_t; + pub static FLECS_IDecs_u8_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_u16_t: ecs_entity_t; + pub static FLECS_IDecs_u16_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_u32_t: ecs_entity_t; + pub static FLECS_IDecs_u32_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_u64_t: ecs_entity_t; + pub static FLECS_IDecs_u64_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_uptr_t: ecs_entity_t; + pub static FLECS_IDecs_uptr_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_i8_t: ecs_entity_t; + pub static FLECS_IDecs_i8_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_i16_t: ecs_entity_t; + pub static FLECS_IDecs_i16_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_i32_t: ecs_entity_t; + pub static FLECS_IDecs_i32_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_i64_t: ecs_entity_t; + pub static FLECS_IDecs_i64_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_iptr_t: ecs_entity_t; + pub static FLECS_IDecs_iptr_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_f32_t: ecs_entity_t; + pub static FLECS_IDecs_f32_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_f64_t: ecs_entity_t; + pub static FLECS_IDecs_f64_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_string_t: ecs_entity_t; + pub static FLECS_IDecs_string_tID_: ecs_entity_t; } extern "C" { - pub static FLECS__Eecs_entity_t: ecs_entity_t; + pub static FLECS_IDecs_entity_tID_: ecs_entity_t; } pub const ecs_type_kind_t_EcsPrimitiveType: ecs_type_kind_t = 0; pub const ecs_type_kind_t_EcsBitmaskType: ecs_type_kind_t = 1; @@ -6394,8 +6551,8 @@ pub const ecs_type_kind_t_EcsArrayType: ecs_type_kind_t = 4; pub const ecs_type_kind_t_EcsVectorType: ecs_type_kind_t = 5; pub const ecs_type_kind_t_EcsOpaqueType: ecs_type_kind_t = 6; pub const ecs_type_kind_t_EcsTypeKindLast: ecs_type_kind_t = 6; -#[doc = "Type kinds supported by reflection type system"] -pub type ecs_type_kind_t = ::std::os::raw::c_int; +#[doc = "Type kinds supported by meta addon"] +pub type ecs_type_kind_t = ::std::os::raw::c_uint; #[doc = "Component that is automatically added to every type with the right kind."] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -6405,10 +6562,6 @@ pub struct EcsMetaType { pub existing: bool, #[doc = "< Is the reflection data a partial type description"] pub partial: bool, - #[doc = "< Computed size"] - pub size: ecs_size_t, - #[doc = "< Computed alignment"] - pub alignment: ecs_size_t, } pub const ecs_primitive_kind_t_EcsBool: ecs_primitive_kind_t = 1; pub const ecs_primitive_kind_t_EcsChar: ecs_primitive_kind_t = 2; @@ -6428,12 +6581,15 @@ pub const ecs_primitive_kind_t_EcsIPtr: ecs_primitive_kind_t = 15; pub const ecs_primitive_kind_t_EcsString: ecs_primitive_kind_t = 16; pub const ecs_primitive_kind_t_EcsEntity: ecs_primitive_kind_t = 17; pub const ecs_primitive_kind_t_EcsPrimitiveKindLast: ecs_primitive_kind_t = 17; -pub type ecs_primitive_kind_t = ::std::os::raw::c_int; +#[doc = "Primitive type kinds supported by meta addon"] +pub type ecs_primitive_kind_t = ::std::os::raw::c_uint; +#[doc = "Component added to primitive types"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsPrimitive { pub kind: ecs_primitive_kind_t, } +#[doc = "Component added to member entities"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsMember { @@ -6442,6 +6598,21 @@ pub struct EcsMember { pub unit: ecs_entity_t, pub offset: i32, } +#[doc = "Type expressing a range for a member value"] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ecs_member_value_range_t { + pub min: f64, + pub max: f64, +} +#[doc = "Component added to member entities to express valid value ranges"] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct EcsMemberRanges { + pub value: ecs_member_value_range_t, + pub warning: ecs_member_value_range_t, + pub error: ecs_member_value_range_t, +} #[doc = "Element type of members vector in EcsStruct"] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -6454,10 +6625,17 @@ pub struct ecs_member_t { pub offset: i32, #[doc = "May be set when used with ecs_struct_desc_t, will be auto-populated if\n type entity is also a unit"] pub unit: ecs_entity_t, + #[doc = "Numerical range that specifies which values member can assume. This\n range may be used by UI elements such as a progress bar or slider. The\n value of a member should not exceed this range."] + pub range: ecs_member_value_range_t, + #[doc = "Numerical range outside of which the value represents an error. This\n range may be used by UI elements to style a value."] + pub error_range: ecs_member_value_range_t, + #[doc = "Numerical range outside of which the value represents an warning. This\n range may be used by UI elements to style a value."] + pub warning_range: ecs_member_value_range_t, #[doc = "Should not be set by ecs_struct_desc_t"] pub size: ecs_size_t, pub member: ecs_entity_t, } +#[doc = "Component added to struct type entities"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsStruct { @@ -6474,6 +6652,7 @@ pub struct ecs_enum_constant_t { #[doc = "Should not be set by ecs_enum_desc_t"] pub constant: ecs_entity_t, } +#[doc = "Component added to enum type entities"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsEnum { @@ -6490,21 +6669,27 @@ pub struct ecs_bitmask_constant_t { #[doc = "Should not be set by ecs_bitmask_desc_t"] pub constant: ecs_entity_t, } +#[doc = "Component added to bitmask type entities"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsBitmask { #[doc = "map"] pub constants: ecs_map_t, } +#[doc = "Component added to array type entities"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsArray { + #[doc = "< Element type"] pub type_: ecs_entity_t, + #[doc = "< Number of elements"] pub count: i32, } +#[doc = "Component added to vector type entities"] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct EcsVector { + #[doc = "< Element type"] pub type_: ecs_entity_t, } #[doc = "Serializer interface"] @@ -6659,7 +6844,7 @@ pub const ecs_meta_type_op_kind_t_EcsOpString: ecs_meta_type_op_kind_t = 24; pub const ecs_meta_type_op_kind_t_EcsOpEntity: ecs_meta_type_op_kind_t = 25; pub const ecs_meta_type_op_kind_t_EcsMetaTypeOpKindLast: ecs_meta_type_op_kind_t = 25; #[doc = "Serializer utilities"] -pub type ecs_meta_type_op_kind_t = ::std::os::raw::c_int; +pub type ecs_meta_type_op_kind_t = ::std::os::raw::c_uint; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_meta_type_op_t { @@ -6673,8 +6858,10 @@ pub struct ecs_meta_type_op_t { pub op_count: i32, #[doc = "< Size of type of operation"] pub size: ecs_size_t, + #[doc = "< Type entity"] pub type_: ecs_entity_t, - pub unit: ecs_entity_t, + #[doc = "< Index of member in struct"] + pub member_index: i32, #[doc = "< string -> member index (structs only)"] pub members: *mut ecs_hashmap_t, } @@ -6793,6 +6980,10 @@ extern "C" { #[doc = "Get member name of current member"] pub fn ecs_meta_get_member(cursor: *const ecs_meta_cursor_t) -> *const ::std::os::raw::c_char; } +extern "C" { + #[doc = "Get member entity of current member"] + pub fn ecs_meta_get_member_id(cursor: *const ecs_meta_cursor_t) -> ecs_entity_t; +} extern "C" { #[doc = "Set field with boolean value"] pub fn ecs_meta_set_bool(cursor: *mut ecs_meta_cursor_t, value: bool) -> ::std::os::raw::c_int; @@ -7224,7 +7415,7 @@ extern "C" { ) -> ::std::os::raw::c_int; } extern "C" { - pub static mut FLECS__EEcsScript: ecs_entity_t; + pub static mut FLECS_IDEcsScriptID_: ecs_entity_t; } #[doc = "Script component"] #[repr(C)] @@ -7539,6 +7730,13 @@ extern "C" { extern "C" { pub fn ecs_cpp_reset_count_inc() -> i32; } +extern "C" { + pub fn ecs_cpp_last_member( + world: *const ecs_world_t, + type_: ecs_entity_t, + ) -> *const ecs_member_t; +} +pub type __builtin_va_list = *mut ::std::os::raw::c_char; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ecs_event_id_record_t { @@ -7546,6 +7744,11 @@ pub struct ecs_event_id_record_t { } #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct ecs_stack_t { + pub _address: u8, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct ecs_table_cache_hdr_t { pub _address: u8, } diff --git a/readme.md b/readme.md index 9c9e50f..ddff7c4 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,7 @@ A Rust binding for the Flecs ECS library: -Wraps native Flecs v3.2.4 +Wraps native Flecs v3.2.8 ## A Simple Example diff --git a/src/component.rs b/src/component.rs index f0b975d..387f1ce 100644 --- a/src/component.rs +++ b/src/component.rs @@ -95,7 +95,7 @@ pub(crate) fn get_component_info( ) -> Option { // flecs stores info about components (size, align) within the world // these are built-in components which we can acess via special component ids - let id = unsafe { FLECS__EEcsComponent }; + let id = unsafe { FLECS_IDEcsComponentID_ }; let raw = unsafe { ecs_get_id(world, comp_e, id) }; if raw.is_null() { return None; diff --git a/src/world.rs b/src/world.rs index b964fa8..826cffb 100644 --- a/src/world.rs +++ b/src/world.rs @@ -571,7 +571,7 @@ impl Drop for World { // Additional Add-ons support impl World { pub fn enable_rest(&self) { - let rest_comp_id = unsafe { FLECS__EEcsRest }; + let rest_comp_id = unsafe { FLECS_IDEcsRestID_ }; let rest_comp_size = std::mem::size_of::(); let rest_data: EcsRest = unsafe { MaybeUninit::zeroed().assume_init() };